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

Я создал службу Windows, которая должна проверять определенную таблицу в БД на новые строки каждые 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...
    }
}

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

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

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

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

Спасибо!

Ответы [ 8 ]

21 голосов
/ 06 августа 2010

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

// 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 не указывает, был ли повторный вход вызван только таймером или была ли службамногопоточный.Предположим позднее, но если первое, то блокировка должна быть ненужной, если таймер остановлен (автоматический сброс или вручную)

20 голосов
/ 06 августа 2010

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

7 голосов
/ 06 августа 2010

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

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

if (Monitor.TryEnter(locker)
{
    try
    {
        // Do your work here.
    }
    finally
    {
        Monitor.Exit(locker);
    }
}
7 голосов
/ 06 августа 2010

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

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 голосов
/ 06 августа 2010

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

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

2 голосов
/ 24 мая 2013

Существует довольно удобный способ решить эту проблему с помощью 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();
2 голосов
/ 06 августа 2010

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

Фоновый работник с легкостью предоставит вам возможность проверить текущую обработку и обрабатывать по 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
    }
0 голосов
/ 06 августа 2010

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

void serviceTimer_Elapsed(object sender, ElapsedEventArgs e)
{   
    if (System.Threading.Monitor.IsLocked(yourLockingObject))
       return;
    else
       lock (yourLockingObject)
       // your logic  
           ;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...