Thread.Interrupt para parar longo sono no desligamento app - Existe uma abordagem melhor
-
16-09-2019 - |
Pergunta
Eu estou tendo uma pequena discussão de fundo que se estende por toda a vida útil aplicações -. No entanto, quando a aplicação é desligamento, o segmento deve sair normalmente
O problema é que o segmento é executado algum código em um intervalo de 15 minutos -. O que significa que dorme um monte
Agora, para tirá-lo do sono, eu atirar uma interrupção para ele - a minha pergunta é, no entanto, se há uma melhor abordagem para isso, já que interrupções gerar ThreadInterruptedException
.Aqui está a essência do meu código (um pouco pseudo):
public class BackgroundUpdater : IDisposable
{
private Thread myThread;
private const int intervalTime = 900000; // 15 minutes
public void Dispose()
{
myThread.Interrupt();
}
public void Start()
{
myThread = new Thread(ThreadedWork);
myThread.IsBackground = true; // To ensure against app waiting for thread to exit
myThread.Priority = ThreadPriority.BelowNormal;
myThread.Start();
}
private void ThreadedWork()
{
try
{
while (true)
{
Thread.Sleep(900000); // 15 minutes
DoWork();
}
}
catch (ThreadInterruptedException)
{
}
}
}
Solução
Não há absolutamente uma maneira melhor - tanto para uso Monitor.Wait
/ pulso em vez de sono / interrupção, ou usar um Auto
/ < a href = "http://msdn.microsoft.com/en-us/library/system.threading.manualresetevent.aspx" rel = "nofollow noreferrer"> ManualResetEvent
. (Você provavelmente quer um ManualResetEvent
neste caso.)
Pessoalmente, eu sou um fã Wait / Pulse, provavelmente devido ao facto de ser como espera de Java () / notify () mecanismo. No entanto, há definitivamente momentos em que eventos de reposição são mais úteis.
Seu código seria algo parecido com isto:
private readonly object padlock = new object();
private volatile bool stopping = false;
public void Stop() // Could make this Dispose if you want
{
stopping = true;
lock (padlock)
{
Monitor.Pulse(padlock);
}
}
private void ThreadedWork()
{
while (!stopping)
{
DoWork();
lock (padlock)
{
Monitor.Wait(padlock, TimeSpan.FromMinutes(15));
}
}
}
Para mais detalhes, consulte o meu rosqueamento tutorial , em particular as páginas < a href = "http://www.yoda.arachsys.com/csharp/threads/deadlocks.shtml" rel = "nofollow noreferrer"> impasses, esperando e pulsando , a página em espera alças . Joe Albahari também tem um tutorial que cobre os mesmos tópicos e compara-los.
Eu não olhei em detalhes ainda, mas eu não ficaria surpreso se extensões paralelas também teve algumas funcionalidades para tornar isso mais fácil.
Outras dicas
Você pode usar um evento para verificar se o processo deve terminar assim:
var eventX = new AutoResetEvent(false);
while (true)
{
if(eventX.WaitOne(900000, false))
{
break;
}
DoWork();
}
Não é de classe CancellationTokenSource
no .NET 4 e mais tarde o que simplifica esta tarefa um pouco.
private readonly CancellationTokenSource cancellationTokenSource =
new CancellationTokenSource();
private void Run()
{
while (!cancellationTokenSource.IsCancellationRequested)
{
DoWork();
cancellationTokenSource.Token.WaitHandle.WaitOne(
TimeSpan.FromMinutes(15));
}
}
public void Stop()
{
cancellationTokenSource.Cancel();
}
Não se esqueça que CancellationTokenSource
é descartável, por isso certifique-se de eliminá-lo corretamente.
Um método poderia ser a de adicionar um evento ou delegado que o fio vai se inscrever para cancelar. Quando a cancelar evento é invocar, a thread pode parar-se.
Eu absolutamente como Jon Skeets resposta. No entanto, este força ser um pouco mais fácil de entender e também deve funcionar:
public class BackgroundTask : IDisposable
{
private readonly CancellationTokenSource cancellationTokenSource;
private bool stop;
public BackgroundTask()
{
this.cancellationTokenSource = new CancellationTokenSource();
this.stop = false;
}
public void Stop()
{
this.stop = true;
this.cancellationTokenSource.Cancel();
}
public void Dispose()
{
this.cancellationTokenSource.Dispose();
}
private void ThreadedWork(object state)
{
using (var syncHandle = new ManualResetEventSlim())
{
while (!this.stop)
{
syncHandle.Wait(TimeSpan.FromMinutes(15), this.cancellationTokenSource.Token);
if (!this.cancellationTokenSource.IsCancellationRequested)
{
// DoWork();
}
}
}
}
}
Ou, incluindo esperando a tarefa de fundo para realmente ter parado (neste caso, descarte deve ser executado por outro segmento do que o segmento de segundo plano está em execução, e é claro que isso não é código perfeito, ele requer o trabalhador thread para realmente ter começado):
using System;
using System.Threading;
public class BackgroundTask : IDisposable
{
private readonly ManualResetEventSlim threadedWorkEndSyncHandle;
private readonly CancellationTokenSource cancellationTokenSource;
private bool stop;
public BackgroundTask()
{
this.threadedWorkEndSyncHandle = new ManualResetEventSlim();
this.cancellationTokenSource = new CancellationTokenSource();
this.stop = false;
}
public void Dispose()
{
this.stop = true;
this.cancellationTokenSource.Cancel();
this.threadedWorkEndSyncHandle.Wait();
this.cancellationTokenSource.Dispose();
this.threadedWorkEndSyncHandle.Dispose();
}
private void ThreadedWork(object state)
{
try
{
using (var syncHandle = new ManualResetEventSlim())
{
while (!this.stop)
{
syncHandle.Wait(TimeSpan.FromMinutes(15), this.cancellationTokenSource.Token);
if (!this.cancellationTokenSource.IsCancellationRequested)
{
// DoWork();
}
}
}
}
finally
{
this.threadedWorkEndSyncHandle.Set();
}
}
}
Se você ver todas as falhas e desvantagens sobre solução Jon Skeets eu gostaria de ouvi-los como eu sempre gosto de aprender ;-) Eu acho que isso é mais lenta e usa mais memória e não deve, portanto, ser usado em grande escala e curto espaço de tempo. Qualquer outra?