2010-08-24 4 views
2

J'ai une bibliothèque de classe C# qui démarre un thread de consommation de fond (paresseusement) qui écoute les tâches à compléter à partir d'une file d'attente producteur/consommateur. Cette bibliothèque de classes peut être utilisée à partir de n'importe quel type d'application .NET et est actuellement utilisée sous un site Web ASP.NET MVC. Le thread consommateur est bloqué la plupart du temps jusqu'à ce qu'une requête arrive dans la file d'attente. Il ne devrait y avoir qu'un seul thread consommateur pour un domaine d'application donné. Je veux être en mesure d'arrêter gracieusement le fil du consommateur lorsque l'application quitte, quel que soit le type d'application, par ex. Application Windows Forms, Console ou WPF. Le code de l'application lui-même devrait ignorer le fait que ce thread reste en attente d'être terminé.Meilleures pratiques de gestion de la durée de vie des threads de base

Comment puis-je résoudre ce problème de durée de vie du thread dans la perspective de la bibliothèque de classes? Y a-t-il un événement global d'arrêt d'application que je peux lier pour abattre le fil gracieusement alors? En ce moment, étant donné qu'il se trouve dans un domaine d'application ASP.NET MVC, cela n'a pas vraiment d'importance puisque ceux-ci ne sont jamais abolis de toute façon. Mais maintenant que je commence à l'utiliser dans des tâches d'applications de console programmées conçues pour se terminer, je veux résoudre ce problème une fois pour toutes. L'application console ne se termine pas car le thread consommateur est toujours actif et bloqué en attente de demandes. J'ai exposé une propriété Thread statique publique sur la classe pour émettre un appel Abort() lorsque l'application console est en cours de sortie, mais c'est franchement dégoûtant.

Tous les pointeurs seraient appréciés! Encore une fois, je ne veux pas avoir à écrire Windows Forms ou WPF ou console code spécifique de l'application pour résoudre le problème. Une bonne solution générique que chaque consommateur de la bibliothèque de classe peut utiliser serait la meilleure.

Répondre

2

Définissez la propriété IsBackground du fil sur true. Alors cela n'empêchera pas le processus de se terminer.

http://msdn.microsoft.com/en-us/library/system.threading.thread.isbackground.aspx

Alternativement, au lieu d'utiliser Abort, utilisez Interrupt. Beaucoup moins dégoûtant.

+0

C'est ce que je cherche! Oui, je sais que «Abandonner()» est dégoûtant; merci pour le conseil sur 'Interrupt()'! –

+0

Vous, monsieur, avez fait ma journée. Merci beaucoup! Plus besoin de l'appel 'Abort()' ou 'Interrupt()'. –

+0

Hmya, il est toujours avorté bien sûr, même si vous n'appelez pas Abort() vous-même. L'effet est identique. –

1

Si vous utilisez .net 4.0, un CancelationToken peut être utilisé pour signaler au thread qu'il est temps de l'arrêter correctement. C'est très utile car la plupart des commandes d'attente vous permettent de lui passer un jeton et si le thread est dans une attente lorsque le thread est annulé, il sortira automatiquement de l'attente.

+0

N'utilisant pas encore .NET 4.0, mais merci pour l'info. –

+1

Ceci est à égalité avec 'Interrupt', mais un peu plus agréable. –

2

C'est un peu difficile. Vous êtes sur la bonne voie pour éviter un abandon, car cela pourrait interrompre le processus au milieu d'une opération importante (écriture dans un fichier, etc.). Définir la propriété IsBackground aurait le même effet.

Ce que vous devez faire est de rendre votre file d'attente de blocage annulable. Dommage que vous n'utilisiez pas .NET 4.0 mais sinon vous auriez pu utiliser la classe BlockingCollection. Pas de soucis cependant. Vous devrez juste modifier votre file d'attente personnalisée afin qu'elle interroge périodiquement un signal d'arrêt. Voici une implémentation rapide et sale que je viens de fouetter en utilisant l'implémentation canonique d'une file d'attente de blocage comme point de départ.

public class CancellableBlockingCollection<T> 
{ 
    private Queue<T> m_Queue = new Queue<T>(); 

    public T Take(WaitHandle cancel) 
    { 
     lock (m_Queue) 
     { 
      while (m_Queue.Count <= 0) 
      { 
       if (!Monitor.Wait(m_Queue, 1000)) 
       { 
        if (cancel.WaitOne(0)) 
        { 
         throw new ThreadInterruptedException(); 
        } 
       } 
      } 
      return m_Queue.Dequeue(); 
     } 
    } 

    public void Add(T data) 
    { 
     lock (m_Queue) 
     { 
      m_Queue.Enqueue(data); 
      Monitor.Pulse(m_Queue); 
     } 
    } 
} 

Remarquez comment la méthode Take accepte un WaitHandle qui peut être testé pour le signal d'annulation. Cela pourrait être la même poignée d'attente utilisée dans d'autres parties de votre code. L'idée ici est que la poignée d'attente est interrogée sur un certain intervalle à l'intérieur de Take et si elle est signalée alors la méthode entière jette.L'hypothèse est que jeter à l'intérieur Take est correct car il est à un point sûr parce que le consommateur ne pouvait pas avoir été au milieu d'une opération importante. Vous pouvez simplement autoriser le thread à se désintégrer sur l'exception à ce stade. Voici à quoi peut ressembler votre thread de consommation.

ManualResetEvent m_Cancel = new ManualResetEvent(false); 
CancellableBlockingCollection<object> m_Queue = new CancellableBlockingCollection<object>(); 

private void ConsumerThread() 
{ 
    while (true) 
    { 
    object item = m_Queue.Take(m_Cancel); // This will throw if the event is signalled. 
    } 
} 

Il existe plusieurs autres façons dont vous pouvez annuler la méthode Take. J'ai choisi d'utiliser un WaitHandle, mais cela pourrait facilement être fait en utilisant un simple drapeau bool par exemple.

Mise à jour:

Comme Steven a souligné dans les commentaires Thread.Interrupt fait essentiellement déjà. Cela entraînera un thread à lancer une exception sur un appel bloquant ... à peu près ce que je recherchais dans cet exemple, mais avec beaucoup plus de code. Un inconvénient avec Thread.Interrupt est qu'il ne fonctionne que pendant les appels de blocage en conserve dans le .NET BCL (comme WaitOne, etc.). Vous ne pourrez donc pas annuler un calcul long en cours comme si vous utilisiez une approche plus manuelle comme celle de cette réponse. C'est certainement un excellent outil à garder dans votre poche arrière et pourrait être utile dans votre scénario spécifique.

+1

Vous pouvez "annuler" une attente en utilisant "Thread.Interrupt", et il n'a pas les horribles effets secondaires de "Thread.Abort". –

+0

@Steven: En effet! –

+0

En réponse à votre mise à jour, une possibilité pourrait être de faire un 'WaitOne (0)' périodiquement. Cela devrait déclencher l'exception d'interruption, sans réellement bloquer. Cela dit, je crois que cela pourrait bien donner le coeur. –

Questions connexes