Use CancellationToken - not Thread.Sleep






4.95/5 (16 votes)
Thread.Sleep cannot be cancelled. To inject a pause, consider using the Cancellation Token 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:
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:
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