Sincronizando um temporizador para evitar sobreposição
-
22-08-2019 - |
Pergunta
Eu estou escrevendo um serviço do Windows que executa uma atividade de comprimento variável em intervalos (uma varredura banco de dados e atualização). Eu preciso esta tarefa para executar com freqüência, mas o código para manipular não é seguro para executar várias vezes ao mesmo tempo.
Como posso mais simplesmente configurar um temporizador para executar a tarefa a cada 30 segundos, sem nunca se sobrepõem execuções? (Estou assumindo System.Threading.Timer
é o temporizador correto para este trabalho, mas poderia ser confundido).
Solução
Você poderia fazê-lo com um temporizador, mas você precisa ter alguma forma de bloqueio em sua digitalização banco de dados e atualização. Um simples lock
para sincronizar pode ser suficiente para impedir que várias execuções ocorram.
Dito isto, talvez seja melhor para iniciar um temporizador depois que você está operação estiver concluída, e usá-lo apenas uma vez, em seguida, parar. Reiniciá-lo após sua próxima operação. Isto lhe daria 30 segundos (ou N segundos) entre os eventos, sem chance de sobreposições, e nenhum bloqueio.
Exemplo:
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);
Work imediatamente ..... Finish ... espere 5 segundos .... trabalhar imediatamente ..... Finish ... espere 5 segundos ....
Outras dicas
Eu usaria Monitor.TryEnter em seu código decorrido:
if (Monitor.TryEnter(lockobj))
{
try
{
// we got the lock, do your work
}
finally
{
Monitor.Exit(lockobj);
}
}
else
{
// another elapsed has the lock
}
Eu prefiro System.Threading.Timer
para coisas como esta, porque eu não tenho que ir através do mecanismo de manipulação de eventos:
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.
}
}
Você pode eliminar a necessidade do bloqueio, fazendo o temporizador de um one-shot e re iniciando-lo após cada actualização:
// 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);
}
em vez de bloqueio (o que poderia causar todos os seus exames programados para esperar e, eventualmente, empilhar). Você poderia começar a / update varredura em um fio e, em seguida, apenas fazer uma verificação para ver se o segmento ainda está vivo.
Thread updateDBThread = new Thread(MyUpdateMethod);
...
private void timer_Elapsed(object sender, ElapsedEventArgs e)
{
if(!updateDBThread.IsAlive)
updateDBThread.Start();
}
Você pode usar o AutoResetEvent da seguinte forma:
// 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...
}
}
}
Um pouco "mais inteligentes" solução é como segue em pseudo-código:
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;
}
}
}
}
Qualquer um destes tem a vantagem de que você pode desligar o fio, apenas por sinalização do evento.
Editar
Devo acrescentar, que este código faz a suposição de que você só tem um desses MyWorkerThreads () em execução, caso contrário eles seriam executados simultaneamente.
Eu usei um mutex quando eu quis execução único:
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();
}
}
}
}
Desculpe o tão tarde ... Você precisará fornecer o nome mutex como parte da chamada de método GetMutexName ().