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

Я создал службу 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 секунд, чтобы проверить снова, чтобы я никогда не застрял в ситуации, когда есть очередь потоков, ожидающих завершения предыдущего.

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

Спасибо!

14
задан kiamlaluno 6 August 2010 в 16:48
поделиться

7 ответов

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

// 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(); 
}

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

21
ответ дан 1 December 2019 в 05:56
поделиться

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

void serviceTimer_Elapsed(object sender, ElapsedEventArgs e)
{   
    if (System.Threading.Monitor.IsLocked(yourLockingObject))
       return;
    else
       lock (yourLockingObject)
       // your logic  
           ;
}
0
ответ дан 1 December 2019 в 05:56
поделиться

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

Поместите это в обработчик события elapsed:

if (Monitor.TryEnter(locker)
{
    try
    {
        // Do your work here.
    }
    finally
    {
        Monitor.Exit(locker);
    }
}
7
ответ дан 1 December 2019 в 05:56
поделиться

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

20
ответ дан 1 December 2019 в 05:56
поделиться

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

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;
    }
}
7
ответ дан 1 December 2019 в 05:56
поделиться

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

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

4
ответ дан 1 December 2019 в 05:56
поделиться

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

Background worker легко даст вам возможность проверять текущую обработку и обрабатывать по 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
    }
2
ответ дан 1 December 2019 в 05:56
поделиться
Другие вопросы по тегам:

Похожие вопросы: