Safely wake and exit a sleeping C# thread without Thread.Abort()

As covered in numerous other posts, Thread.Abort() is a dangerous approach to exiting a thread and can have many unintended consequences that are difficult to track down.

In Azure Web Farm, I ran into a situation where on a role stopping, I needed to safely terminate multiple running threads (some of which had regular Thread.Sleep() calls in between processing loops).

You can solve this problem using the amazing Task Parallel Library in combination with Cancellation Tokens and Wait Handles. There are definitely many other ways to do this - I'll first demo two solutions that aren't as safe and explain why.

Option 1 - Thread.Abort()

The following is an example of solving this problem with Thread.Abort() - simple, but can cause unintended side effects in other areas of your application:

private readonly Thread _thread;

public void Start()
{
    _thread = new Thread(() =>
    {
        while (true)
        {
            // Perform check or processing

            Thread.Sleep(TimeSpan.FromSeconds(10));
        }
    });
}

public void Stop()
{
    _thread.Abort();
}

Option 2 - TPL with Monitor Sync

To improve upon aborting the thread directly, we can wake up the thread in the middle of Thread.Sleep() and safely end it using TPL and Locks:

private CancellationTokenSource _cancellationTokenSource;
private readonly object _lockObject = new object();

public void Start()
{
    // Create a new Cancellation Token Source - this contains a shared object used to pass a cancel message to TPL
    _cancellationTokenSource = new CancellationTokenSource();
    Task.Factory.StartNew(() =>
    {
        while (true)
        {
            // Perform check or processing

            lock (_lockObject)
            {
                // Same as a Thread.Sleep(), but it will wake up if signalled by Monitor.Pulse()
                Monitor.Wait(_lockObject, TimeSpan.FromSeconds(10));
                // After 10 seconds have passed or we have been signalled, check the Cancellation Token
                if (_cancellationTokenSource.Token.IsCancellationRequested)
                    return;
            }
        }
    }, _cancellationTokenSource.Token);
}

public void Stop()
{
    // Set the Cancellation Token status to Cancelled
    _cancellationTokenSource.Cancel();
    // Wake up the running thread if it is currently sleeping in a Monitor.Wait()
    lock (_lockObject)
    {
        Monitor.Pulse(_lockObject);
    }
}

Option 3 - TPL with Reset Events (Safest solution I can find)

The above solution is a definite improvement, but Monitor pulses can be lost between threads in certain race conditions (see This excellent StackOverflow post for more detail). A safer alternative is therefore to use reset events, at the potential cost of slightly lower performance than locking. See the final solution below:

private CancellationTokenSource _cancellationTokenSource;
private ManualResetEvent _resetEvent;

public void Start()
{
    _cancellationTokenSource = new CancellationTokenSource();
    // Set up a signal object in a default false state, to be used for waking up the Start() thread if sleeping
    _resetEvent = new ManualResetEvent(false);

    Task.Factory.StartNew(() =>
    {
        while (true)
        {
            // Perform check or processing

            _resetEvent.WaitOne(TimeSpan.FromSeconds(10));
            if (_cancellationTokenSource.Token.IsCancellationRequested)
                return;
        }
    }, _cancellationTokenSource.Token);
}

public void Stop()
{
    // First set the cancellation token to Cancelled
    _cancellationTokenSource.Cancel();
    // Now wake up the Start() thread if it is currently sleeping
    _resetEvent.Set();
}
Tweet