2010-09-13 2 views
7

déclaration ProblèmeComment mettre fin à un correctement thread de travail en C#

J'ai un thread de travail qui scanne essentiellement un dossier, d'entrer dans les fichiers qu'il contient, et dort ensuite pendant un certain temps. L'opération de numérisation peut prendre 2-3 secondes mais pas beaucoup plus. Je cherche un moyen d'arrêter ce fil avec élégance.

Précision: Je veux arrêter le fil alors qu'il est dormir, et non pendant qu'il est balayage. Cependant, le problème est que je ne sais pas quel est l'état actuel du thread. S'il dort, je veux qu'il sorte immédiatement. Si c'est la numérisation, je veux qu'il sorte du moment où il essaie de bloquer.

Les tentatives d'une solution

Au début, je mise en veille et d'interruption. Ensuite, j'ai découvert que l'interruption n'interrompt pas vraiment le sommeil - cela ne fonctionne que lorsque les threads essaye de dormir.

Donc, je suis passé à Monitor Wait & Pulse. Puis j'ai découvert que le Pulse ne fonctionne que lorsque je suis en attente. Alors maintenant j'ai un fil qui ressemble à ça:

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

Et maintenant j'ai besoin de créer ma fonction Stop. Alors j'ai commencé avec:

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

Mais cela ne fonctionne pas parce que je peux être pulsant pendant que les travaux de fil (alors qu'il est pas en attente). J'ai donc ajouté Interrompre:

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

Une autre option consiste à utiliser:

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

La question

Quelle est la méthode préférée? Y a-t-il une troisième méthode préférable?

+0

« * Au début, j'utilisais sommeil et Interrompre Puis j'ai découvert Cette interruption n'interrompt pas vraiment le sommeil - elle ne fonctionne que lorsque les threads essaient de dormir. * "C'est étrange. Est-ce un bug connu? Si ce n'est pas le cas, pouvez-vous essayer de le reproduire avec un exemple de jouet? – Heinzi

Répondre

7

Une autre alternative consiste à utiliser des événements:

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

Oui, cela fonctionnera aussi bien. La question est de savoir quelle serait la meilleure façon de le faire. Votre option semble meilleure que l'impulsion récurrente ou l'impulsion d'interruption +. –

+0

+1, oui, c'est * loin * mieux. –

+1

Eh bien, personnellement, je n'utiliserais pas Pulse/Interrupts. Le pouls pourrait être problématique en raison des APC possibles (je crois que cela pourrait passer inaperçu par le thread). Les interruptions n'ont pas une bonne réputation: http://www.bluebytesoftware.com/blog/2007/08/23/ThreadInterruptsAreAlmostAsEvilAsThreadAborts.aspx – liggett78

9

La façon d'arrêter un fil avec élégance est de le laisser finir par lui-même. Donc, à l'intérieur de la méthode worker, vous pouvez avoir une variable booléenne qui va vérifier si nous voulons interrompre. Par défaut, il sera défini sur false et lorsque vous le définissez sur true à partir du thread principal, il arrêtera simplement l'opération d'analyse en rompant avec la boucle de traitement.

+3

+1 pour permettre au thread de terminer par lui-même. Toute autre approche est désordonnée. N'oubliez pas de marquer le drapeau booléen avec le mot clé volatile. – spender

+1

Merci. Vous remarquerez que j'ai un tel drapeau. Je n'interromps pas le fil pendant qu'il fonctionne, mais je veux l'interrompre pendant qu'il dort. Si ça va dormir 10 minutes, je ne veux pas que ça continue à dormir. –

+0

Un thread qui dort pendant 10 secondes n'est utile à personne. Utilisez le ThreadPool pour dessiner des threads lorsque vous avez besoin d'effectuer certaines tâches mais ne les laissez pas dormir. Faites-leur faire des trucs utiles. –

1

je vous recommande de le garder simple:

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

Cela présente les avantages suivants:

  • Il sent comme attente occupé, mais ce n'est pas. $ NUMBER_OF_SECONDS vérifications sont effectuées au cours de la phase d'attente, ce qui n'est pas comparable aux milliers de contrôles effectués dans l'attente réelle occupé.
  • C'est simple, ce qui réduit considérablement le risque d'erreur dans le code multithread. Tout ce que votre méthode Stop doit faire est de mettre m_shouldRun à false et (peut-être) d'appeler Thread.Join (s'il est nécessaire que le thread se termine avant que Stop ne reste).Aucune primitive de synchronisation n'est nécessaire (sauf pour marquer m_shouldRun comme volatil).
+0

La fonction DoSomething ne sera pas interrompue de force. Thread.Interrupt "arrive" seulement quand le thread essaie de bloquer. Voir la documentation MS (ici: http://msdn.microsoft.com/en-us/library/system.threading.thread.interrupt.aspx) - "Si ce thread n'est pas actuellement bloqué dans une attente, une veille ou une jointure état, il sera interrompu quand il commence à bloquer. " –

+0

@Eldad: Bon point, je l'ai confondu avec Thread.Abort. Changé ma réponse. – Heinzi

0

je suis venu avec la programmation séparément la tâche.

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