2010-09-13 7 views
7

ProblemstellungWie ein Worker-Thread richtig in C#

Ich habe einen Worker-Thread zu beenden, die im Grunde einen Ordner durchsucht, gehen in die Akten innerhalb und schlafen dann für eine Weile. Der Scanvorgang dauert 2-3 Sekunden, aber nicht viel mehr. Ich suche nach einer Möglichkeit, diesen Faden elegant zu stoppen.

Klärung: Ich möchte den Faden zu stoppen, während es Schlaf, ist und nicht, wenn esScannen ist. Das Problem ist jedoch, dass ich nicht weiß, was der aktuelle Status des Threads ist. Wenn es schläft, möchte ich es sofort verlassen. Wenn es scannt, möchte ich, dass es den Moment beendet, wenn es versucht zu blockieren.

Versuche einer Lösung

Zuerst war ich mit Schlaf und Interrupt. Dann habe ich herausgefunden, dass Interrupt den Schlaf nicht wirklich unterbricht - es funktioniert nur, wenn die Threads versuchen, in den Schlaf zu gehen.

Also wechselte ich auf Monitor Warten & Pulse. Dann habe ich herausgefunden, dass der Pulse nur funktioniert, wenn ich tatsächlich in der Wartestellung bin. So, jetzt habe ich einen Faden, der so aussieht:

while (m_shouldRun) 
{ 
    try 
    { 
     DoSomethingThatTakesSeveralSeconds(); 
     lock (this) 
     { 
      Monitor.Wait(this, 5000); 
     } 
    } 
    catch (ThreadInterruptedException) 
    { 
     m_shouldRun = false; 
    } 
} 

Und jetzt muss ich meine Stop Funktion Handwerk. Also begann ich mit:

public void Stop() 
{ 
    m_shouldRun = false; 
    lock (this) 
    { 
     Monitor.Pulse(this); 
    } 
    thread.Join(); 
} 

Aber das funktioniert nicht, weil ich während der Faden Werke pulsieren werden kann (während es nicht zu warten). So habe ich Interrupt:

public void Stop() 
{ 
    m_shouldRun = false; 
    thread.Interrupt(); 
    lock (this) 
    { 
     Monitor.Pulse(this); 
    } 
    thread.Join(); 
} 

Eine weitere Option ist die Verwendung:

public void Stop() 
{ 
    m_shouldRun = false; 
    while (!thread.Join(1000)) 
    { 
     lock (this) 
     { 
      Monitor.Pulse(this); 
     } 
    } 
} 

Die Frage

Was ist die bevorzugte Methode? Gibt es eine dritte Methode, die vorzuziehen ist?

+0

„* Zuerst I Sleep and Interrupt wurde mit Dann fand ich heraus, Dieser Interrupt unterbricht den Schlaf nicht wirklich - er funktioniert nur, wenn die Threads versuchen, schlafen zu gehen. * "Das ist seltsam. Ist das ein bekannter Fehler? Wenn nicht, können Sie versuchen, es mit einem Spielzeugbeispiel zu reproduzieren? – Heinzi

Antwort

7

Eine weitere Alternative ist Ereignisse zu verwenden:

private ManualResetEvent _event = new ManualResetEvent(false); 


public void Run() 
{ 
while (true) 
{ 
    DoSomethingThatTakesSeveralSeconds(); 
    if (_event.WaitOne(timeout)) 
     break; 
} 
} 

public void Stop() 
{ 
    _event.Set(); 
    thread.Join(); 
} 
+0

Ja, das wird auch funktionieren. Die Frage ist, was wäre der beste Weg, dies zu tun. Ihre Option sieht besser aus als der wiederkehrende Impuls oder der Interrupt + Impuls. –

+0

+1, ja, das ist * weit * besser. –

+1

Nun, persönlich würde ich Pulse/Interrupts nicht verwenden. Der Puls könnte aufgrund möglicher APCs problematisch sein (ich glaube, dass der Thread dies nicht bemerkt). Interrupts haben keinen guten Ruf: http://www.bluebytesoftware.com/blog/2007/08/23/ThreadInterruptsAreAlmostAsEvilAsThreadAborts.aspx – liggett78

9

Der Weg, um einen Faden elegant zu stoppen, ist, ihn von selbst zu beenden. Also könnten Sie innerhalb der Worker-Methode eine boolesche Variable haben, die prüft, ob wir unterbrechen wollen. Standardmäßig wird es auf false gesetzt und wenn Sie es vom Hauptthread auf true setzen, stoppt es einfach den Scanvorgang, indem es die Verarbeitungsschleife unterbricht.

+3

+1, damit der Thread selbst beendet werden kann. Jeder andere Ansatz ist unordentlich. Vergessen Sie nicht, das boolesche Flag mit dem Schlüsselwort volatile zu markieren. – spender

+1

Danke. Du wirst bemerken, dass ich eine solche Flagge habe. Ich unterbreche den Thread nicht, während er tatsächlich funktioniert, aber ich möchte ihn unterbrechen, während er schläft. Wenn es 10 Minuten schlafen soll, möchte ich nicht, dass es weiter schläft. –

+0

Ein Thread, der 10 Sekunden lang schläft, ist für niemanden nützlich. Verwenden Sie den 'ThreadPool', um Threads zu zeichnen, wann immer Sie einige Aufgaben ausführen müssen, aber lassen Sie sie nicht schlafen. Lass sie nützliche Dinge tun. –

1

Ich empfehle es einfach zu halten:

while (m_shouldRun) 
{ 
    DoSomethingThatTakesSeveralSeconds(); 
    for (int i = 0; i < 5; i++) // example: 5 seconds sleep 
    { 
     if (!m_shouldRun) 
      break; 
     Thread.Sleep(1000); 
    } 
} 

public void Stop() 
{ 
    m_shouldRun = false; 
    // maybe thread.Join(); 
} 

Dies hat folgende Vorteile:

  • Es riecht nach busy waiting, aber es ist nicht. $ NUMBER_OF_SECONDS-Überprüfungen werden während der Wartephase durchgeführt, die nicht mit den Tausenden von Überprüfungen verglichen werden kann, die bei echtem Busy-Warten durchgeführt werden.
  • Es ist einfach, was das Risiko von Fehlern in Multi-Thread-Code erheblich reduziert. Alle Ihre Stop Methode muss tun, um m_shouldRun zu false und (vielleicht) Thread.Join (wenn es notwendig ist, den Thread zu beenden, bevor Stop übrig ist) zu setzen.Es sind keine Synchronisationsprimitiven erforderlich (außer dass m_shouldRun als flüchtig markiert wird).
+0

Die DoSomething-Funktion wird nicht zwangsweise unterbrochen. Thread.Interrupt nur "passiert", wenn der Thread versucht zu blockieren. Siehe die MS-Dokumentation (hier: http://msdn.microsoft.com/en-us/library/system.threading.thread.interrupt.aspx) - "Wenn dieser Thread derzeit nicht in einem Warte-, Ruhezustand- oder Join-Modus blockiert ist Zustand, es wird unterbrochen, wenn es als nächstes zu blockieren beginnt. " –

+0

@Eldad: Guter Punkt, ich habe es mit Thread.Abort verwechselt. Änderte meine Antwort. – Heinzi

0

ich mit separat Planung der Aufgabe kam.

using System; 
using System.Threading; 

namespace ProjectEuler 
{ 
    class Program 
    { 
     //const double cycleIntervalMilliseconds = 10 * 60 * 1000; 
     const double cycleIntervalMilliseconds = 5 * 1000; 
     static readonly System.Timers.Timer scanTimer = 
      new System.Timers.Timer(cycleIntervalMilliseconds); 
     static bool scanningEnabled = true; 
     static readonly ManualResetEvent scanFinished = 
      new ManualResetEvent(true); 

     static void Main(string[] args) 
     { 
      scanTimer.Elapsed += 
       new System.Timers.ElapsedEventHandler(scanTimer_Elapsed); 
      scanTimer.Enabled = true; 

      Console.ReadLine(); 
      scanningEnabled = false; 
      scanFinished.WaitOne(); 
     } 

     static void scanTimer_Elapsed(object sender, 
      System.Timers.ElapsedEventArgs e) 
     { 
      scanFinished.Reset(); 
      scanTimer.Enabled = false; 

      if (scanningEnabled) 
      { 
       try 
       { 
        Console.WriteLine("Processing"); 
        Thread.Sleep(5000); 
        Console.WriteLine("Finished"); 
       } 
       finally 
       { 
        scanTimer.Enabled = scanningEnabled; 
        scanFinished.Set(); 
       } 
      } 
     } 
    } 
}