views:

1263

answers:

3

I have an object in C# on which I need to execute a method on a regular basis. I would like this method to be executed only when other people are using my object, as soon as people stop using my object I would like this background operation to stop.

So here is a simple example is this (which is broken):

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

The problem is that if I new a Fish object anywhere, it never gets garbage collected, cause there is a background thread referencing it. Here is an illustrated version of broken code.

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

I know that the Fish object should be disposable and I should clean up the thread on dispose, but I have no control over my callers and can not ensure dispose is called.

How do I work around this problem and ensure the background threads are automatically disposed even if Dispose is not called explicitly?

+2  A: 

I think the IDisposable solution is the correct one.

If the users of your class don't follow the guidelines for using classes that implement IDisposable it's their fault - and you can make sure that the documentation explicitly mentions how the class should be used.

Another, much messier, option would be a "KeepAlive" DateTime field that each method called by your client would update. The worker thread then checks the field periodically and exits if it hasn't been updated for a certain amount of time. When a method is setting the field the thread will be restarted if it has exited.

Andrew Kennan
Well, this does not really work for me cause I am writing a plugin for a tool that does not call dispose on the interface I implement, its completely out of my control. Besides this technique is actually pretty useful in isolating buggy code that omits the dispose cause I can log it.
Sam Saffron
Fair enough. It would be nice if the tool designers had considered that you might want to know when the plugin was finished with :]
Andrew Kennan
+2  A: 

This is how I would do it:

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();
     }
    }
}
thealliedhacker
You could also call Join with a timeout and abort the thread if needed. In that case you would want to wrap BackgroundWork with an exception handler for ThreadAbortedException. You might need this if you want to make sure the thread stops and the "Swim" method takes a long time.
thealliedhacker
This solution doesn't really work. The thread will not terminate if dispose is not called ...
Sam Saffron
+3  A: 

Here is my proposed solution to this problem:

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