2009-07-17 6 views
5

J'ai un petit thread d'arrière-plan qui court pour la durée de vie des applications - cependant lorsque l'application est arrêtée, le thread devrait quitter normalement.Thread.Interrupt pour arrêter le long sommeil à l'arrêt de l'application - Y at-il une meilleure approche

Le problème est que le thread exécute du code à un intervalle de 15 minutes - ce qui signifie qu'il dort ALOT.

Maintenant, afin de le sortir de sommeil, je lance une interruption à elle - ma question est cependant, s'il y a une meilleure approche à cela, puisque les interruptions génèrent ThreadInterruptedException.

est ici l'essentiel de mon code (un peu 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) 
     { 
     } 
    } 
} 

Répondre

14

Il n'y a absolument une meilleure façon - soit utiliser Monitor.Wait/Pulse au lieu de sommeil/Interrompre ou utilisez un Auto/ManualResetEvent. (Vous voudrez probablement un ManualResetEvent dans ce cas.)

Personnellement, je suis un ventilateur Wait/Pulse, probablement en raison de son mécanisme wait()/notify() de Java. Cependant, il y a certainement des moments où les événements de réinitialisation sont plus utiles.

Votre code ressemblerait à quelque chose comme ceci:

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)); 
     } 
    } 
} 

Pour plus de détails, voir mon threading tutorial, en particulier les pages sur deadlocks, waiting and pulsing, la page sur wait handles. Joe Albahari also has a tutorial qui couvre les mêmes sujets et les compare.

Je n'ai pas encore examiné en détail, mais je ne serais pas surpris si les extensions parallèles avaient aussi quelques fonctionnalités pour rendre cela plus facile.

+0

Vous êtes trop rapide. J'abandonne;) – frast

+0

Grande solution, merci beaucoup :-) – Steffen

+2

N'est pas le problème ici que le thread de travail attend en tenant le verrou, de sorte que le blocage de la méthode stop() bloquerait jusqu'à 15 minutes jusqu'à la thread de travail libère le verrou? –

0

Une méthode peut être d'ajouter un événement cancel ou un délégué auquel le thread va s'abonner. Lorsque l'événement cancel est invoqué, le thread peut s'arrêter lui-même.

2

Vous pouvez utiliser un événement pour vérifier si le processus devrait se terminer comme ceci:

var eventX = new AutoResetEvent(false); 
while (true) 
{ 
    if(eventX.WaitOne(900000, false)) 
    { 
     break; 
    } 
    DoWork(); 
} 
0

Je suis absolument comme réponse Jon Skeets. Cependant, cette pourrait être un peu plus facile à comprendre et devrait également fonctionner:

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, y compris en attendant la tâche d'arrière-plan ont effectivement arrêté (dans ce cas, Éliminez doit être invoqué par un autre fil que la un fil d'arrière-plan est en cours d'exécution sur, et bien sûr ce n'est pas un code parfait, il faut le thread de travail pour avoir réellement commencé):

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(); 
     } 
    } 
} 

Si vous voyez des défauts et des inconvénients sur une solution Jon Skeets je voudrais pour les entendre comme j'apprécie toujours apprendre ;-) Je suppose que c'est s inférieur et utilise plus de mémoire et ne devrait donc pas être utilisé à grande échelle et à court terme. Tout autre?

1

Il existe une classe CancellationTokenSource dans .NET 4 et versions ultérieures qui simplifie un peu cette tâche.

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(); 
} 

Ne pas oublier que CancellationTokenSource est disponible, assurez-vous de disposer correctement.

Questions connexes