Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / programming / threads

Use CancellationToken - not Thread.Sleep

4.95/5 (17 votes)
13 May 2020CPOL2 min read 27K   226  
Thread.Sleep cannot be cancelled. To inject a pause, consider using the Cancellation Token instead.
It is quite common to discover a need to inject a pause in the code execution, a remote target or file may not be ready at first glance, so you back off for a period and try again, repeating until the target is ready or the process is cancelled. In such a case, it is tempting to put in a Thread.Sleep(n) - but this will not respond to a cancellation trigger, so why not use the CancellationToken instead?

Introduction

Not so long ago, I was asked to take a look at a service which was intermittently generating exceptions on stop and shutdown operations.

The service itself was a simple device management component, using a task to connect to a remote device when it had booted up, doing some updates and that was sort of it. But the target device was not always ready when the connection attempt was made, so a while loop was used with a pause injected in the form of a call to Thread.Sleep(20000).

The idea of pausing a task for 20 seconds odd, before trying again makes perfect sense - but the 20 second thread block would intermittently, dependent on timing, cause the service manager to forcibly kill off the service due to shutdown or stop timeouts being triggered, and resulting exceptions would fill the logs.

Thread.Sleep(n) cannot be cancelled - instead, consider using the CancellationToken.WaitHandle.WaitOne(n).

Using the Code

The code in this tip is a little sample of how Thread.Sleep and CancellationToken.WaitHandle.WaitOne behave in tasks, that you can experiment with.

The original while loop implemented in the service looked something like this:

C#
while (!cancellationToken.IsCancellationRequested)
{
    // Processing
    if(connectionReady)
    {
        // Do its business
        break;
    }
    // Pause for 20 seconds before trying again
    Thread.Sleep(20000);
}

In this implementation, the thread will be blocked for 20 seconds - irrespective of any cancellation triggers. This was the underlying problem - dependent on the timing of the stop operation in relation to the Thread.Sleep call, the service manager would timeout the stop operation and forcibly kill the management service.

Pausing code execution, but being cancellation request aware is simple enough:

C#
while (!cancellationToken.IsCancellationRequested) 
{ 
    // Processing
    if(connectionReady)
    {
        // Do its business
        break;
    }
    // Pause for 20 seconds before trying again - now with cancellation support
    var cancellationTriggered = cancellationToken.WaitHandle.WaitOne(20000);
}

This one line change achieves the same 20 second pause that the original implementation did, and it is also task cancellation aware.

Points of Interest

If you find Thread.Sleep(n) used around the place, consider switching to using the CancellationToken.WaitHandle.WaitOne(n) method instead. It will help in keeping the exceptions log down to size.

History

  • 13th May, 2020: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)