Синхронизация таймера для предотвращения перекрытия
Я пишу службу Windows, которая периодически запускает операцию переменной длины (сканирование и обновление базы данных). Мне нужно, чтобы эта задача выполнялась часто, но код для обработки небезопасен для запуска несколько раз одновременно.
Как я могу просто настроить таймер для запуска задачи каждые 30 секунд, не перекрывая выполнение? (Я предполагаю, что System.Threading.Timer
является правильным таймером для этого задания, но может быть ошибочным).
Ответы
Ответ 1
Вы можете сделать это с помощью таймера, но вам нужно будет иметь некоторую форму блокировки при сканировании и обновлении базы данных. Простой lock
для синхронизации может быть достаточным для предотвращения нескольких прогонов.
Как говорится, лучше начать таймер ПОСЛЕ завершения операции и просто использовать ее один раз, а затем остановить ее. Перезапустите его после следующей операции. Это даст вам 30 секунд (или N секунд) между событиями, без шансов наложения и без блокировки.
Пример:
System.Threading.Timer timer = null;
timer = new System.Threading.Timer((g) =>
{
Console.WriteLine(1); //do whatever
timer.Change(5000, Timeout.Infinite);
}, null, 0, Timeout.Infinite);
Работа сразу..... Закончить... Подождать 5 сек.... Работа сразу..... Закончить... Подождать 5 сек....
Ответ 2
Я бы использовал Monitor.TryEnter в вашем прошедшем коде:
if (Monitor.TryEnter(lockobj))
{
try
{
// we got the lock, do your work
}
finally
{
Monitor.Exit(lockobj);
}
}
else
{
// another elapsed has the lock
}
Ответ 3
Я предпочитаю System.Threading.Timer
для таких вещей, потому что мне не нужно проходить механизм обработки событий:
Timer UpdateTimer = new Timer(UpdateCallback, null, 30000, 30000);
object updateLock = new object();
void UpdateCallback(object state)
{
if (Monitor.TryEnter(updateLock))
{
try
{
// do stuff here
}
finally
{
Monitor.Exit(updateLock);
}
}
else
{
// previous timer tick took too long.
// so do nothing this time through.
}
}
Вы можете устранить необходимость блокировки, сделав таймер одним выстрелом и перезапустив его после каждого обновления:
// Initialize timer as a one-shot
Timer UpdateTimer = new Timer(UpdateCallback, null, 30000, Timeout.Infinite);
void UpdateCallback(object state)
{
// do stuff here
// re-enable the timer
UpdateTimer.Change(30000, Timeout.Infinite);
}
Ответ 4
вместо блокировки (что может привести к тому, что все ваши временные проверки ожидают и в конечном итоге складываются). Вы можете запустить сканирование/обновление в потоке, а затем просто выполнить проверку, чтобы убедиться, что поток все еще жив.
Thread updateDBThread = new Thread(MyUpdateMethod);
...
private void timer_Elapsed(object sender, ElapsedEventArgs e)
{
if(!updateDBThread.IsAlive)
updateDBThread.Start();
}
Ответ 5
Вы можете использовать AutoResetEvent следующим образом:
// Somewhere else in the code
using System;
using System.Threading;
// In the class or whever appropriate
static AutoResetEvent autoEvent = new AutoResetEvent(false);
void MyWorkerThread()
{
while(1)
{
// Wait for work method to signal.
if(autoEvent.WaitOne(30000, false))
{
// Signalled time to quit
return;
}
else
{
// grab a lock
// do the work
// Whatever...
}
}
}
В псевдокоде есть несколько "более умное" решение:
using System;
using System.Diagnostics;
using System.Threading;
// In the class or whever appropriate
static AutoResetEvent autoEvent = new AutoResetEvent(false);
void MyWorkerThread()
{
Stopwatch stopWatch = new Stopwatch();
TimeSpan Second30 = new TimeSpan(0,0,30);
TimeSpan SecondsZero = new TimeSpan(0);
TimeSpan waitTime = Second30 - SecondsZero;
TimeSpan interval;
while(1)
{
// Wait for work method to signal.
if(autoEvent.WaitOne(waitTime, false))
{
// Signalled time to quit
return;
}
else
{
stopWatch.Start();
// grab a lock
// do the work
// Whatever...
stopwatch.stop();
interval = stopwatch.Elapsed;
if (interval < Seconds30)
{
waitTime = Seconds30 - interval;
}
else
{
waitTime = SecondsZero;
}
}
}
}
Любое из этих преимуществ имеет то преимущество, что вы можете отключить поток, просто сигнализируя о событии.
Edit
Я должен добавить, что этот код делает предположение, что вы используете только один из этих MyWorkerThreads(), иначе они будут запускаться одновременно.
Ответ 6
Я использовал мьютекс, когда мне нужно одно выполнение:
private void OnMsgTimer(object sender, ElapsedEventArgs args)
{
// mutex creates a single instance in this application
bool wasMutexCreatedNew = false;
using(Mutex onlyOne = new Mutex(true, GetMutexName(), out wasMutexCreatedNew))
{
if (wasMutexCreatedNew)
{
try
{
//<your code here>
}
finally
{
onlyOne.ReleaseMutex();
}
}
}
}
Извините, я так опоздал... Вам нужно указать имя мьютекса как часть вызова метода GetMutexName().