2010-09-13 10 views
7

problema dichiarazioneCome terminare un thread di lavoro in modo corretto in C#

Ho un thread di lavoro che esegue la scansione fondamentalmente una cartella, entrare nei file al suo interno, e poi dorme per un po '. L'operazione di scansione potrebbe richiedere 2-3 secondi ma non molto di più. Sto cercando un modo per fermare questa discussione elegantemente.

Chiarimento: voglio fermare il filo mentre è dormire, e non mentre è scansione. Tuttavia, il problema è che non so quale sia lo stato corrente del thread. Se sta dormendo, voglio che esca immediatamente. Se è in scansione, voglio che esca dal momento in cui tenta di bloccare.

I tentativi di una soluzione

All'inizio ero usando Sonno e interrupt. Poi ho scoperto che Interrupt non interrompe veramente lo Sleep - funziona solo quando i thread cercano di andare a dormire.

Quindi sono passato a Monitor Attendi & Impulso. Poi ho scoperto che Pulse funziona solo quando sono effettivamente nell'Attesa. Così ora ho una discussione che assomiglia a quella:

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

E ora ho bisogno di creare la mia funzione di arresto. Così ho iniziato con:

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

Ma questo non funziona perché io possa essere pulsare mentre le opere di filo (mentre non è in attesa). Così ho aggiunto interrupt:

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

Un'altra opzione è quella di utilizzare:

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

La domanda

Qual è il metodo preferito? C'è un terzo metodo che è preferibile?

+0

"* In un primo momento ero con sonno e interrupt Poi ho scoperto che Interrupt non interrompe veramente il Sonno - funziona solo quando i fili cercano di addormentarsi. * "È strano. È un bug noto? Se non lo è, puoi provare a riprodurlo con un esempio di giocattolo? – Heinzi

risposta

7

Un'altra alternativa è quella di utilizzare gli eventi:

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

Sì, funzionerà anche così. La domanda è quale sarebbe il modo migliore per farlo. L'opzione ha un aspetto migliore rispetto a Pulse ricorrente o Interrupt + Pulse. –

+0

+1, sì, questo è * lontano * migliore. –

+1

Beh, personalmente non userei Pulse/Interrupts. Pulse potrebbe essere problematico a causa di possibili APC (credo che potrebbe passare inosservato dal thread). Gli interrupt non hanno una buona reputazione: http://www.bluebytesoftware.com/blog/2007/08/23/ThreadInterruptsAreAlmostAsEvilAsThreadAborts.aspx – liggett78

9

Il modo per fermare un thread in modo elegante è lasciarlo terminare da solo. Quindi all'interno del metodo worker potresti avere una variabile booleana che controllerà se vogliamo interrompere. Per impostazione predefinita, verrà impostato su false e quando lo si imposta su true dal thread principale interromperà semplicemente l'operazione di scansione interrompendo il ciclo di elaborazione.

+3

+1 per consentire al thread di terminare da solo. Qualsiasi altro approccio è disordinato. Non dimenticare di contrassegnare la bandiera booleana con la parola chiave volatile. – spender

+1

Grazie. Noterai che ho una tale bandiera. Non interrompo il thread mentre funziona, ma voglio interromperlo mentre sta dormendo. Se dormirà 10 minuti, non voglio che continui a dormire. –

+0

Un thread che sta dormendo per 10 secondi non è utile a nessuno. Usa il 'ThreadPool' per disegnare i thread ogni volta che devi svolgere alcune attività ma non lasciarli dormire. Fagli fare cose utili. –

1

vi consiglio di mantenere le cose semplici:

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

Questo ha i seguenti vantaggi:

  • odora occupato in attesa, ma non è. $ NUMBER_OF_SECONDS controlli vengono eseguiti durante la fase di attesa, che non è paragonabile alle migliaia di controlli effettuati in attesa reale occupato.
  • È semplice, il che riduce notevolmente il rischio di errori nel codice multi-thread. Tutto il tuo metodo Stop deve essere impostato su m_shouldRun su falso e (forse) chiamare Thread.Join (se è necessario che il thread termini prima che sia lasciato Stop).Non sono necessarie primitive di sincronizzazione (ad eccezione del contrassegno m_shouldRun come volatile).
+0

La funzione DoSomething non verrà interrotta forzatamente. Thread.Interrupt solo "succede" quando il thread tenta di bloccare. Vedere la documentazione di MS (qui: http://msdn.microsoft.com/en-us/library/system.threading.thread.interrupt.aspx) - "Se questo thread non è attualmente bloccato in attesa, sospensione o partecipazione stato, verrà interrotto quando inizierà a bloccare. " –

+0

@Eldad: buon punto, l'ho confuso con Thread.Abort. Ho cambiato la mia risposta. – Heinzi

0

mi è venuta a parte la pianificazione del compito:.

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