2008-12-10 17 views
3

J'ai un objet en C# sur lequel j'ai besoin d'exécuter une méthode sur une base régulière. Je voudrais que cette méthode soit exécutée seulement quand d'autres personnes utilisent mon objet, dès que les gens arrêtent d'utiliser mon objet je voudrais que cette opération d'arrière-plan s'arrête.Terminer automatiquement les threads non essentiels en C#

Voici donc un exemple simple est ce (qui est cassé):

class Fish 
{ 
    public Fish() 
    { 
     Thread t = new Thread(new ThreadStart(BackgroundWork));  
     t.IsBackground = true; 
     t.Start(); 
    } 

    public void BackgroundWork() 
    { 
     while(true) 
     { 
      this.Swim(); 
      Thread.Sleep(1000); 
     } 
    } 


    public void Swim() 
    { 
     Console.WriteLine("The fish is Swimming"); 
    } 
} 

Le problème est que si je nouveau un objet de poisson partout, il ne fait jamais les déchets collectés, la cause il y a un référencement de fil d'arrière-plan il. Voici une version illustrée de code cassé.

public void DoStuff() 
{ 
    Fish f = new Fish(); 
} 
// after existing from this method my Fish object keeps on swimming. 

Je sais que l'objet de poisson devrait être disponible et je devrais nettoyer le fil sur Éliminez, mais je n'ai aucun contrôle sur mes appels et ne peut pas assurer Éliminez est appelé. Comment puis-je contourner ce problème et m'assurer que les threads d'arrière-plan sont automatiquement éliminés même si Dispose n'est pas appelé explicitement?

Répondre

4

Voici ma solution proposée à ce problème:

class Fish : IDisposable 
{ 
    class Swimmer 
    { 
     Thread t; 
     WeakReference fishRef; 
     public ManualResetEvent terminate = new ManualResetEvent(false); 

     public Swimmer(Fish3 fish) 
     { 
      this.fishRef = new WeakReference(fish); 
      t = new Thread(new ThreadStart(BackgroundWork));  
      t.IsBackground = true; 
      t.Start(); 
     } 

     public void BackgroundWork() 
     { 
      bool done = false; 
      while(!done) 
      { 
       done = Swim(); 
       if (!done) 
       { 
        done = terminate.WaitOne(1000, false); 
       } 
      } 
     } 

     // this is pulled out into a helper method to ensure 
     // the Fish object is referenced for the minimal amount of time 
     private bool Swim() 
     { 
      bool done; 

      Fish fish = Fish; 
      if (fish != null) 
      { 
       fish.Swim(); 
       done = false; 
      } 
      else 
      { 
       done = true; 
      } 
      return done; 
     } 

     public Fish Fish 
     { 
      get { return fishRef.Target as Fish3; } 
     } 
    } 

    Swimmer swimmer; 

    public Fish() 
    { 
      swimmer = new Swimmer(this); 
    } 

    public void Swim() 
    { 
     Console.WriteLine("The third fish is Swimming"); 
    } 

    volatile bool disposed = false; 

    public void Dispose() 
    { 
     if (!disposed) 
     { 
      swimmer.terminate.Set(); 
      disposed = true; 
      GC.SuppressFinalize(this); 
     }  
    } 

    ~Fish() 
    { 
     if(!disposed) 
     { 
      Dispose(); 
     } 
    } 
} 
2

Je pense que la solution IDisposable est la bonne.

Si les utilisateurs de votre classe ne suivent pas les instructions d'utilisation des classes implémentant IDisposable, c'est leur faute - et vous pouvez vous assurer que la documentation mentionne explicitement comment la classe doit être utilisée.

Une autre option beaucoup plus compliquée serait un champ DateTime "KeepAlive" que chaque méthode appelée par votre client mettrait à jour. Le thread de travail vérifie alors périodiquement le champ et quitte s'il n'a pas été mis à jour depuis un certain temps. Lorsqu'une méthode définit le champ, le thread sera redémarré s'il a quitté.

+0

Eh bien, cela ne fonctionne pas vraiment pour moi parce que je suis en train d'écrire un plugin pour un outil qui n'appelle pas disposer sur l'interface que j'implémente, c'est complètement hors de mon contrôle. En outre, cette technique est en fait très utile pour isoler le code bogué qui omet la cause de l'élimination, je peux l'enregistrer. –

+0

Assez juste. Ce serait bien si les concepteurs d'outils avaient pensé que vous pourriez vouloir savoir quand le plugin a été fini avec:] –

2

Voilà comment je le ferais:

class Fish3 : IDisposable 
{ 
    Thread t; 
    private ManualResetEvent terminate = new ManualResetEvent(false); 
    private volatile int disposed = 0; 

    public Fish3() 
    { 
     t = new Thread(new ThreadStart(BackgroundWork)); 
     t.IsBackground = true; 
     t.Start(); 
    } 

    public void BackgroundWork() 
    { 
     while(!terminate.WaitOne(1000, false)) 
     { 
      Swim();   
     } 
    } 

    public void Swim() 
    { 
     Console.WriteLine("The third fish is Swimming"); 
    } 

    public void Dispose() 
    { 
     if(Interlocked.Exchange(ref disposed, 1) == 0) 
     { 
      terminate.Set(); 
      t.Join(); 
      GC.SuppressFinalize(this); 
     } 
    } 

    ~Fish3() 
    { 
     if(Interlocked.Exchange(ref disposed, 1) == 0) 
     { 
      Dispose(); 
     } 
    } 
} 
+0

Vous pouvez également appeler Join avec un timeout et annuler le thread si nécessaire. Dans ce cas, vous voudriez ajouter à BackgroundWork un gestionnaire d'exception pour ThreadAbortedException. Vous pourriez en avoir besoin si vous voulez vous assurer que le fil s'arrête et que la méthode "Swim" prend beaucoup de temps. –

+0

Cette solution ne fonctionne pas vraiment. Le thread ne se terminera pas si disposer n'est pas appelé ... –

Questions connexes