Как позволить таймеру пропустить галочку, если предыдущий поток все еще занят
Я создал службу 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
;
}