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:
while (!cancellationToken.IsCancellationRequested)
{
if(connectionReady)
{
break;
}
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)
{
if(connectionReady)
{
break;
}
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