2009-07-17 8 views
5

Ich habe einen kleinen Hintergrund-Thread, der für die Lebensdauer der Anwendungen ausgeführt wird - aber wenn die Anwendung heruntergefahren wird, sollte der Thread ordnungsgemäß beendet werden.Thread.Interrupt, um langen Schlaf bei App-Shutdown zu stoppen - Gibt es einen besseren Ansatz

Das Problem ist, dass der Thread einige Code in einem Intervall von 15 Minuten ausführt - was bedeutet, dass es viel schläft.

Jetzt, um es aus dem Schlaf zu bekommen, ich werfen einen Interrupt - meine Frage ist jedoch, wenn es einen besseren Ansatz dazu ist, da Interrupts ThreadInterruptedException generieren.

Hier ist der Kern meines Code (etwas 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) 
     { 
     } 
    } 
} 

Antwort

14

Es gibt absolut eine bessere Art und Weise - entweder Monitor.Wait/Pulse statt Schlaf/Interrupt verwenden, oder verwenden Sie ein Auto/ManualResetEvent. (Wahrscheinlich würden Sie in diesem Fall eine ManualResetEvent wollen.)

Persönlich bin ich ein Wait/Pulse-Fan, wahrscheinlich weil es wie Java warte()/notify() -Mechanismus ist. Es gibt jedoch definitiv Zeiten, in denen Reset-Ereignisse nützlicher sind.

Ihr Code würde wie folgt aussehen:

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

Weitere Einzelheiten meine threading tutorial sehen, insbesondere die Seiten auf deadlocks, waiting and pulsing, die Seite auf wait handles. Joe Albahari also has a tutorial, die die gleichen Themen behandelt und vergleicht sie.

Ich habe noch nicht im Detail geschaut, aber ich wäre nicht überrascht, wenn Parallel Extensions auch einige Funktionen haben, um dies zu erleichtern.

+0

Sie sind zu schnell. Ich gebe auf;) – frast

+0

Tolle Lösung, vielen Dank :-) – Steffen

+2

Ist hier nicht das Problem, dass der Arbeitsfaden beim Festhalten der Sperre wartet, so dass die Sperre in der stop() -Methode für bis zu 15 Minuten blockieren würde bis die Arbeitsfaden löst das Schloss? –

0

Eine Methode könnte darin bestehen, ein Cancel-Ereignis oder Delegate hinzuzufügen, das der Thread abonnieren wird. Wenn das Ereignis cancel aufgerufen wird, kann der Thread sich selbst stoppen.

2

Sie könnten ein Event prüfen verwenden, wenn der Prozess so enden sollte:

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

ich absolut wie Jon Skeets. Doch diese Macht ein bisschen leichter zu verstehen und soll auch funktionieren:

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

Oder auch für die Hintergrundaufgabe wartet tatsächlich aufgehört haben zu (in diesem Fall muss Entsorgen von anderem Thread aufgerufen werden, als die eine der Hintergrund-Thread auf ausgeführt wird, und das ist natürlich nicht perfekt Code, bedarf es der worker-Thread tatsächlich begonnen haben, zu):

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

Wenn Sie irgendwelche Mängel und Nachteile gegenüber Jon Skeets Lösung sehe ich möchte um sie zu hören, weil ich immer gerne lerne ;-) Ich denke, das ist s niedriger und verwendet mehr Speicher und sollte daher nicht in einem großen Maßstab und kurzen Zeitrahmen verwendet werden. Irgendwelche anderen?

1

Es gibt CancellationTokenSource Klasse in .NET 4 und später, die diese Aufgabe ein wenig vereinfacht.

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

Vergessen Sie nicht, dass CancellationTokenSource Einweg ist, so stellen Sie sicher, dass Sie es richtig entsorgen.

Verwandte Themen