Как позволить таймеру пропустить галочку, если предыдущий поток все еще занят

Я создал службу Windows, которая должна проверять определенную таблицу в db для новых строк каждые 60 секунд. Для каждой новой добавленной строки мне нужно сделать некоторую тяжелую обработку на сервере, которая иногда может занимать более 60 секунд.

Я создал объект Timer в своей службе, который гаснет каждые 60 секунд и вызывает требуемый метод.
Поскольку я не хочу, чтобы этот таймер указывал при обработке найденных новых строк, я завернул этот метод в блок lock { }, поэтому он не будет доступен другим потоком.

Это выглядит примерно так:

Timer serviceTimer = new Timer();
serviceTimer.Interval = 60;
serviceTimer.Elapsed += new ElapsedEventHandler(serviceTimer_Elapsed);
serviceTimer.Start();

void serviceTimer_Elapsed(object sender, ElapsedEventArgs e)
{
    lock (this)
    {
        // do some heavy processing...
    }
}

Теперь мне интересно -
Если мой таймер тикает и находит много новых строк на db, и теперь обработка займет более 60 секунд, следующий тик не будет обрабатывать до завершения предыдущего. Это тот эффект, который я хочу.

Но теперь будет ли метод serviceTimer_Elapsed отключен сразу после завершения первой обработки или будет ждать, пока таймер снова отметит.

Я хочу, чтобы это произошло - если для обработки требуется более 60 секунд, чем таймер заметит, что поток заблокирован, и подождите еще 60 секунд, чтобы снова проверить, чтобы я никогда не застрял в ситуации, когда есть очередь потоков, ожидающих завершения предыдущего.

Как я могу выполнить этот результат?
Какова наилучшая практика для этого?

Спасибо!

Ответы

Ответ 1

Вы можете попробовать отключить таймер во время обработки, что-то вроде

// Just in case someone wants to inherit your class and lock it as well ...
private static object _padlock = new object();
try
{
  serviceTimer.Stop(); 

  lock (_padlock)
    { 
        // do some heavy processing... 
    } 
}
finally
{
  serviceTimer.Start(); 
}

Изменить: OP не указал, было ли повторное подключение вызвано только таймером или была ли услуга многопоточной. Предположим, что позже, но если первая, то блокировка должна быть ненужной, если таймер остановлен (AutoReset или вручную)

Ответ 2

В этом случае блокировка не нужна. Перед запуском установите timer.AutoReset = false. Перезагрузите таймер в обработчике после завершения обработки. Это гарантирует, что таймер срабатывает через 60 секунд после каждой задачи.

Ответ 3

Попробуйте быстро проверить, работает ли служба. если он запущен, он пропустит это событие и дождитесь следующего.

Timer serviceTimer = new Timer();
serviceTimer.Interval = 60;
serviceTimer.Elapsed += new ElapsedEventHandler(serviceTimer_Elapsed);
serviceTimer.Start();
bool isRunning = false;
void serviceTimer_Elapsed(object sender, ElapsedEventArgs e)
{
    lock (this)
    {
        if(isRunning)
            return;
        isRunning = true;
    }
    try
    {
    // do some heavy processing...
    }
    finally
    {
        isRunning = false;
    }
}

Ответ 4

Аналогичная вариация в других ответах, которая позволяет таймеру продолжать тикать и выполнять работу только тогда, когда блокировка может быть получена, вместо остановки таймера.

Поместите это в обработчик прошедшего события:

if (Monitor.TryEnter(locker)
{
    try
    {
        // Do your work here.
    }
    finally
    {
        Monitor.Exit(locker);
    }
}

Ответ 5

Я рекомендую вам не позволять таймеру вообще тикать во время его обработки.

Установите для параметра Автосохранение таймеров значение false. И начните это в конце. Здесь полный ответ, который может вас заинтересовать  Нужен: служба Windows, которая выполняет задания из очереди заданий в БД; Требуется: Пример кода

Ответ 6

Другие варианты могут заключаться в использовании класса BackGroundWorker или TheadPool.QueueUserWorkItem.

Фоновый работник легко предоставит вам опцию check для текущей обработки, которая все еще происходит и обрабатывает 1 элемент за раз. ThreadPool даст вам возможность продолжать размещать элементы в каждом тике (если необходимо) в фоновом потоке.

Из вашего описания я предполагаю, что вы проверяете элементы в очереди в базе данных. В этом случае я бы использовал ThreadPool, чтобы подтолкнуть работу к фону, а не замедлить/остановить механизм проверки.

Для службы я бы действительно предложил вам взглянуть на подход ThreadPool. Таким образом, вы можете проверять новые предметы каждые 60 секунд с помощью своего таймера, затем опускать их в очередь и разрешать .Net определять, сколько нужно выделять каждому элементу, и просто продолжать толкать элементы в очередь.

Пример: Если вы просто используете таймер, и у вас есть 5 новых строк, для которых требуется 65 секунд общего времени обработки. Используя подход ThreadPool, это будет сделано через 65 секунд, с 5 рабочими элементами фона. Используя подход Таймер, это займет 4 + минуты (в минуту, когда вы будете ждать между каждой строкой), плюс это может привести к обратному журналу другой работы, которая находится в очереди.

Вот пример того, как это должно быть сделано:

Timer serviceTimer = new Timer();
    void startTimer()
    {
        serviceTimer.Interval = 60;
        serviceTimer.Elapsed += new ElapsedEventHandler(serviceTimer_Elapsed);
        serviceTimer.AutoReset = false;
        serviceTimer.Start();
    }
    void serviceTimer_Elapsed(object sender, ElapsedEventArgs e)
    {
        try
        {
            // Get your rows of queued work requests

            // Now Push Each Row to Background Thread Processing
            foreach (Row aRow in RowsOfRequests)
            {
                ThreadPool.QueueUserWorkItem(
                    new WaitCallback(longWorkingCode), 
                    aRow);
            }
        }
        finally
        {
            // Wait Another 60 Seconds and check again
            serviceTimer.Stop();
        }
    }

    void longWorkingCode(object workObject)
    {
        Row workRow = workObject as Row;
        if (workRow == null)
            return;

        // Do your Long work here on workRow
    }

Ответ 7

Существует довольно аккуратный способ решить это с помощью Reactive Extensions. Здесь код, и вы можете прочитать более полное объяснение здесь: http://www.zerobugbuild.com/?p=259

public static IDisposable ScheduleRecurringAction(
    this IScheduler scheduler,
    TimeSpan interval,
    Action action)
{
    return scheduler.Schedule(
        interval, scheduleNext =>
    {
        action();
        scheduleNext(interval);
    });
}

И вы можете использовать его следующим образом:

TimeSpan interval = TimeSpan.FromSeconds(5);
Action work = () => Console.WriteLine("Doing some work...");

var schedule = Scheduler.Default.ScheduleRecurringAction(interval, work);          

Console.WriteLine("Press return to stop.");
Console.ReadLine();
schedule.Dispose();

Ответ 8

другая возможность была бы примерно такой:

void serviceTimer_Elapsed(object sender, ElapsedEventArgs e)
{   
    if (System.Threading.Monitor.IsLocked(yourLockingObject))
       return;
    else
       lock (yourLockingObject)
       // your logic  
           ;
}