2010-09-28 7 views
5

Bonjour! Supposons que nous avons la classe suivante:Implémentation IDisposable pour la classe qui contient des threads

class MultithreadOperation : IDisposable 
{ 
    private IList<Thread> operationThreads; 

    public void StartOperation() 
    { 
      // Initialize and start threads and put them to operationThreads 
    } 

    public void StopOperation() 
    { 
      // Aborts each thread. 
    } 

    public void Dispose() 
    { 
      Dispose(true); 
      GC.SuppressFinalize(this); 
    } 

    protected virtual void Dispose(bool disposing) 
    { 
     if (!disposed) 
     { 
       disposed = true; 
       if (disposing) 
       { 
        // Release managed resources. 
        #1: 
        StopOperation(); 
       } 
       // Release unmanaged resources. 
       #2: 
       StopOperation(); 
     } 
    } 

    ~MultithreadOperation() 
    { 
     Dispose(false); 
    } 
} 

En fait, je dois arrêter tous les fils si l'instance est disposé. Aussi, j'ai besoin d'arrêter tous les threads si l'instance est garbage collectée (sinon, les threads seront encore vivants, ce qui est mauvais pour moi). Certainement, il est tout à fait légal d'invoquer la méthode StopOperation() à la place # 1.

Je veux savoir s'il y a des pièges si nous invoquons StopOperation() à l'endroit n ° 2? Si je comprends bien, la liste des threads peut déjà être collectée lorsque le ~ MultithreadOperation() est en cours d'exécution. En outre, j'ai vu beaucoup de recommandations pour éviter tout code qui fait référence aux ressources gérées dans l'implémentation Finalize, en particulier les champs d'instance.

En outre, il serait intéressant d'entendre des approches différentes pour ce problème. Merci!

+0

double possible de [C# Finaliser/pattern Dispose] (http://stackoverflow.com/questions/898828/c-finalize-dispose-pattern) – x0n

+0

vote pour fermer. Cela a été demandé et répondu un million de fois partout sur le web. remplacer "thread" par "objet géré" et la question est un doublon. – x0n

+0

Que diriez-vous que le Thread sera toujours vivant après que MutilthreadOperation ait été récupéré? Ce comportement est assez mauvais. Le sujet que vous avez référé suggère de ne pas arrêter le thread dans le finaliseur. – Alexander

Répondre

5

Ceci est complètement inapproprié. Un utilisateur de votre classe qui ne connaît pas bien les détails supposera qu'appeler Dispose() fera quelque chose d'innocent. Un peu de nettoyage, rien d'extraordinaire. Elle va pas attendre que le programme soit laissé dans un état complètement imprévisible avec des threads mutant cet état avorté à des endroits aléatoires sans moyen de récupérer l'état.

Il est également complet sur le thread finalizer, l'abandon d'un thread peut prendre plusieurs secondes si le thread n'est pas dans un état d'alerte. Le CLR annulera le thread du finaliseur au bout de 2 secondes et terminera le programme, ne donnant aucun diagnostic sur la véritable raison du plantage du programme.

+0

Merci. Cela rend la situation claire. – Alexander

+0

Devine Je suis un peu en retard ici, mais qu'en est-il lorsque les threads sont sortis proprement dans StopOperation()? De plus, qu'en est-il des cas où Dispose n'a pas été appelé? –

2

Il n'est pas sûr de placer un appel à StopOperation en position # 2 parce que:

  • Le List de fils ou eux-mêmes instances Thread ont peut-être déjà été recueillies.
  • Il n'est pas clair si votre StopOperation bloque jusqu'à ce que tous les threads aient acquitté un signal d'arrêt, mais si c'est le cas, il peut raccrocher le thread du finaliseur.

Un autre point est que vous ne devriez pas abandonner les threads (via Thread.Abort) dans ce cas. La raison en est que l'abandon injecte une exception hors-bande (asynchrone) dans le thread cible aux points non déterministes. Parce que ces points d'injection sont aléatoires (et souvent non intuitifs), ils peuvent corrompre l'état (en renonçant tôt au milieu d'une écriture ou d'une autre opération plus complexe) dans le domaine d'application ou même tout le processus.

La meilleure façon de gérer cela est de faire ce qui suit:

  • Demandez le sondage de fil ou écouter un signal d'arrêt. Vous pouvez voir my answer here pour obtenir des conseils sur la façon de procéder. Avoir le signal StopOperation pour signaler le mécanisme d'arrêt que vous avez choisi. Habituellement, vous voulez attendre que tous les threads aient reçu le signal et se soient arrêtés d'eux-mêmes. Vous pouvez attendre une discussion en appelant le Thread.Join.
  • Appelez StopOperation dans la méthode Dispose à la position 1 uniquement.

Maintenant, comment pouvons-nous traiter avec les appelants qui ne jouent pas bien et éviter d'appeler Dispose? C'est un peu plus compliqué. Je vois deux modèles généraux pour faire face à cela.

  • Demandez au finaliseur de basculer un drapeau d'arrêt. Ce drapeau doit être un type de valeur sinon il serait sujet à la récupération de place. Chaque thread doit interroger périodiquement ce drapeau. Cela signifie que vous ne devez effectuer aucun appel bloquant qui attend indéfiniment. Rappelez-vous, vous ne pouvez pas appeler Thread.Interrupt pour piquer un fil car cela nécessite une référence à une instance Thread.
  • Conserver une liste de threads actifs dans une variable de collection statique de manière à de les énumérer en toute sécurité dans un finaliseur. Cependant, je dois avouer que je suis un peu rouillé sur les règles de collecte concernant les références statiques lors des déchargements de domaine d'application (c'est-à-dire l'arrêt de l'application). Donc, je ne suis pas certain que ce soit entièrement en toute sécurité.
Questions connexes