Я создал службу 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 секунд, чтобы проверить снова, чтобы я никогда не застрял в ситуации, когда есть очередь потоков, ожидающих завершения предыдущего.
Как мне достичь этого результата?
Как лучше всего это делать?
Спасибо!
Вы можете попробовать отключить таймер во время обработки, что-то вроде
// 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 не указывал, было ли повторное вхождение вызвано только таймером или служба была многопоточной. . Предположили, что второе, но если первое, то в блокировке не будет необходимости, если таймер остановлен (автосброс или вручную)
другая возможность могла бы выглядеть примерно так:
void serviceTimer_Elapsed(object sender, ElapsedEventArgs e)
{
if (System.Threading.Monitor.IsLocked(yourLockingObject))
return;
else
lock (yourLockingObject)
// your logic
;
}
Похожая вариация на другие ответы, которая позволяет таймеру продолжать тикать и выполнять работу только тогда, когда блокировка может быть получена, вместо того, чтобы останавливать таймер.
Поместите это в обработчик события elapsed:
if (Monitor.TryEnter(locker)
{
try
{
// Do your work here.
}
finally
{
Monitor.Exit(locker);
}
}
В этом случае замок вам не нужен. Перед запуском установите timer.AutoReset = false. После завершения обработки перезапустите таймер в обработчике. Это гарантирует, что таймер срабатывает через 60 секунд после каждой задачи.
Поставьте быструю проверку, посмотрите, работает ли служба. если он запущен, он пропустит это событие и дождется запуска следующего.
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;
}
}
Я рекомендую вам вообще не позволять таймеру срабатывать во время его обработки.
Установите для автосброса таймеров значение false. И начинать в конце. Вот полный ответ, который может вас заинтересовать Требуется: служба Windows, которая выполняет задания из очереди заданий в базе данных; Разыскивается: пример кода
Другими вариантами может быть использование класса 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
}