BlackWaspTM

This web site uses cookies. By using the site you accept the cookie policy.This message is for compliance with the UK ICO law.

.NET Framework
.NET 1.1+

IDisposable and Thread Safety

The IDisposable interface should be applied to classes that hold unmanaged resources, either directly or indirectly. When developing using parallel programming or multi-threading techniques, IDisposable should be implemented in a thread-safe manner.

IDisposable Interface

IDisposable is a standard interface in the .NET framework. It is implemented by classes that hold managed resources or instances of other types that also implement IDisposable. The interface includes a single method, Dispose, which can be called to immediately release the unmanaged resources and free up memory or system resources accordingly. When implemented correctly, disposable classes also enable the garbage collector to free up those resources, preventing leaks.

If you are developing multi-threaded software or are using parallelism, the implementation of IDisposable should be thread-safe. In this article we will see how this can be achieved.

Thread-Safe IDisposable

To begin, we'll look at some code that shows a standard implementation of IDisposable but that is not thread-safe. I won't describe the implementation. If you are would like more information about the basic pattern, read the "Implementing IDisposable" article. The code below shows the sample class. It does not hold any unmanaged resources directly so no finalizer has been included. It does hold a reference to a Stream object, which is disposed of correctly in the protected Dispose method.

public class DisposableResource : IDisposable
{
    bool _disposed;
    Stream _stream;

    public DisposableResource()
    {
        _stream = File.OpenRead(@"c:\test\test.txt");
    }

    public long FileLength()
    {
        if (_disposed) throw new ObjectDisposedException(
            typeof(DisposableResource).FullName);
        return _stream.Length;
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if (disposing)
            {
                if (_stream != null) _stream.Dispose();
            }

            _stream = null;
            _disposed = true;
        }
    }

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

Adding Thread-Safety

The above implementation presents several problems when accessed by several threads concurrently. Each is a bug that would only be seen intermittently and could be difficult to find. The problems include:

  • It is possible for two threads to access the unmanaged resource at the same time. If the resource is not thread-safe this could give unexpected results.
  • It is possible for one thread to start disposing resources whilst another uses them. This would happen if both threads passed the check that _disposed is false, before one disposed of the resource and the other tried to use it. Using a disposed object or released resource could cause an exception or unexpected side effects.
  • Two threads could attempt to release the resources at the same time. In the example code this could lead to an ObjectDisposedException.

Adding Locking

We can resolve all of the described issues with the correct application of locking. We need to add locks to all of the sections of the code that use the held resources to prevent more than one thread accessing any of those critical sections at the same time. This will ensure that the Dispose method will never be executed whilst the resources are in use and that two threads cannot simultaneously access a resource.

First we need an object that will control the locks. This will be shared by all locks so is created as with a class-level scope. In this case we don't want other classes to be able to lock on the same object so we'll keep the object private.

To add the lock object, we can add the following declaration to the class:

object _lock = new object();

We can now define critical sections of the code wherever we want to limit access to a single thread. First, all methods and properties that access the held resources should be locked. In our case this means the FileLength property. In your own classes there may be many more members to lock.

The FileLength property should be modified as shown below. Note that the lock statement appears before the check for the object being disposed. If the if statement appeared before the lock it would be possible for a thread to be blocked whilst the object is disposed. After disposal, the thread would become unblocked and attempt to use the disposed resource.

public long FileLength()
{
    lock (_lock)
    {
        if (_disposed) throw new ObjectDisposedException(
            typeof(DisposableResource).FullName);
        return _stream.Length;
    }
}

The next change is to the Dispose method. We don't want other members to be called during a dispose operation. Nor do we wish to allow Dispose to execute when another thread is using a held resource or attempting to release it. We therefore add a lock to the entire method, as shown below:

protected virtual void Dispose(bool disposing)
{
    lock (_lock)
    {
        if (!_disposed)
        {
            if (disposing)
            {
                if (_stream != null) _stream.Dispose();
            }

            _stream = null;
            _disposed = true;
        }
    }
}

Using Finalizers

If you are using unmanaged resources directly you should add a finalizer that calls Dispose, so that the garbage collector can correctly free up those resources. You should ensure that any actions that are performed within the finalizer are thread-safe.

Although the sample class doesn't need it, the code below shows how a finalizer could be added. No locking is required as this is taken care of by the Dispose method.

~DisposableResource()
{
    Dispose(false);
}
1 November 2011