2009-07-17 8 views
5

Mam wątek w tle, który działa przez cały okres użytkowania aplikacji - jednak gdy aplikacja zostanie zamknięta, wątek powinien zakończyć się z gracją.Wątek Przerwij, aby zatrzymać długi sen przy wyłączaniu aplikacji - czy istnieje lepsze podejście?

Problem polega na tym, że wątek uruchamia kod w odstępie 15 minut - co oznacza, że ​​śpi w trybie ALOT.

Teraz, aby wydostać się z tego uśpienia, rzucam na niego przerwę - moje pytanie brzmi jednak, czy istnieje lepsze podejście do tego, ponieważ przerwania generują wyjątek ThreadInterruptedException.

Oto sedno mojego kodu (nieco 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) 
     { 
     } 
    } 
} 

Odpowiedz

14

nie ma absolutnie lepszy sposób - albo użyć Monitor.Wait/Pulse zamiast uśpienia/przerwania, lub użyć Auto/ManualResetEvent. (W tym przypadku prawdopodobnie będziesz potrzebował ManualResetEvent).

Osobiście jestem fanem Wait/Pulse, prawdopodobnie dlatego, że jest to mechanizm Java wait()/notify(). Jednak zdarzają się sytuacje, w których resetowanie zdarzeń jest bardziej użyteczne.

Twój kod będzie wyglądał tak:

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

Aby uzyskać więcej informacji, zobacz moje threading tutorial, w szczególności stron na deadlocks, waiting and pulsing, stronę wait handles. Joe Albahari also has a tutorial, który obejmuje te same tematy i porównuje je.

Nie zajrzałem jeszcze szczegółowo, ale nie zdziwiłbym się, gdyby rozszerzenia równoległe miały również trochę funkcji, aby to ułatwić.

+0

Jesteś zbyt szybki. Poddaję się;) – frast

+0

Świetne rozwiązanie, dziękuję bardzo :-) – Steffen

+2

Nie jest to problem, że wątek roboczy czeka podczas trzymania zamka, więc blokada w metodzie stop() będzie blokować do 15 minut aż do wątek roboczy zwalnia blokadę? –

0

Jedną z metod może być dodanie zdarzenia anulowania lub delegowanie, do którego subskrybuje wątek. Gdy wywoływane jest zdarzenie cancel, wątek może się zatrzymać.

2

Można użyć zdarzenia, aby sprawdzić, czy proces powinien zakończyć tak:

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

absolutnie jak Jon Skeets odpowiedź. Jednak ta potęga być nieco łatwiejsze do zrozumienia i powinna także działać:

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

Albo, w tym oczekiwania na zadania w tle rzeczywiście przestali (w tym przypadku, rozporządzania musi być wywołana przez innego wątku niż z jeden wątek tła jest uruchomiony, i oczywiście nie jest to idealne kod, wymaga wątku roboczego faktycznie rozpoczęły):

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

Jeśli widzisz jakieś wady i zalety ponad roztworem Jon Skeets chciałbym słyszeć je tak, jak zawsze lubię się uczyć ;-) Chyba to s niżej i zużywa więcej pamięci, dlatego nie należy go stosować na dużą skalę i w krótkim czasie. Jakieś inne?

1

Istnieje klasa CancellationTokenSource w .NET 4 i późniejszych, co nieco upraszcza to zadanie.

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

nie należy zapominać, że CancellationTokenSource jest jednorazowy, więc upewnij się, wyrzucać go prawidłowo.

Powiązane problemy