pixartist
pixartist

Reputation: 1155

Is it OK to stop a thread from the owning objects Dispose method?

Here is a simple example:

class MyClass : IDisposable
{
    bool working;
    public MyClass()
    {
        working = true;
        Thread t = new Thread(Worker);
        t.Start();
    }
    void Worker()
    {
        while(working)
        {}
    }
    public void Dispose()
    {
        working = false;
    }
}

Is it okay to do this ? Will Dispose() even be called while the thread is still running? If not, how to better do it ?

Upvotes: 1

Views: 492

Answers (2)

Vlad
Vlad

Reputation: 3171

You shouldn't rely on that your Dispose method will be called.

You can wrap you class instance with WeakReference. The thread should stop its work once the reference is garbage collected.

class MyClass : IDisposable
{
    class CancellationSignal
    {
        WeakReference _reference;
        public bool IsWorking { get { return _working && _reference.IsAlive; } }
        volatile bool _working;

        public CancellationSignal(WeakReference reference)
        {
            if (reference == null) throw new ArgumentNullException("reference");
            _reference = reference;
        }

        public void Signal()
        {
            _working = false;
        }

    }

    CancellationSignal _cancellationSignal;

    public MyClass()
    {
        _cancellationSignal = new CancellationSignal(new WeakReference(this));
        Thread t = new Thread(Worker);
        t.Start(_cancellationSignal);
    }

    static void Worker(object state)
    {
        var s = (CancellationSignal)state;
        while (s.IsWorking)
        {
            //...
        }
    }

    public void Dispose()
    {
        _cancellationSignal.Signal();
    }
}

Personally I prefer to avoid using finalizers but I still have to say that another solution would be calling Signal in finalizer (and then you can remove WeakReference stuff):

 ~MyClass()
 {
    _cancellationSignal.Signal();
 }

Notice that _working is declared as volatile. You can also use ManualResetEventSlim or even CancellationToken instead.

In both approaches (WeakReference and finalizer) you have to avoid passing this reference to Thread (because it would prevent your class instance from being garbage collected) therefore I added a CancellationSignal class and marked Worker as static.

Upvotes: 1

Habib
Habib

Reputation: 223187

Dispose will not be called unless you call it, either explicitly or through enclosing MyClass instantiation in using statement.

Will Dispose() even be called while the thread is still running?

It can be called like:

using (MyClass m = new MyClass())
{
    System.Threading.Thread.Sleep(2000);
}

So once the using block ends, it will call the Dispose method in your class.

(Although your variable should be volatile to indicate that the field might be modified by multiple threads)

But the important thing to note is, that there is no magical thing about Dispose, even the using statement translates into try-finally , where Dispose is explicitly called in the finally block.

Upvotes: 1

Related Questions