What\'s the best way to sleep a certain amount of time, but be able to be interrupted by a IsCancellationRequested
from a CancellationToken
?
I just blogged about it here:
CancellationToken and Thread.Sleep
in Short:
var cancelled = token.WaitHandle.WaitOne(TimeSpan.FromSeconds(5));
In your context:
void MyFunc (CancellationToken ct)
{
//...
// simulate some long lasting operation that should be cancelable
var cancelled = ct.WaitHandle.WaitOne(TimeSpan.FromSeconds(10));
}
The best solution I found so far is:
void MyFunc(CancellationToken ct)
{
//...
var timedOut = WaitHandle.WaitAny(new[] { ct.WaitHandle }, TimeSpan.FromMilliseconds(2000)) == WaitHandle.WaitTimeout;
var cancelled = ! timedOut;
}
UPDATE:
The best solution so far is the accepted answer.
Alternatively, I think this is pretty clear:
Task.Delay(waitTimeInMs, cancellationToken).Wait(cancellationToken);
The CancellationToken.WaitHandle can throw an exception when accessed after the CancellationTokenSource has been disposed:
ObjectDisposedException: The CancellationTokenSource has been disposed.
In some cases, especially when linked cancellation sources are being manually disposed (as they should be), this can be a nuisance.
This extension method allows 'safe cancellation waiting'; however, it should be used on conjunction with checks to, and proper flagging of, the cancellation token's state and/or usage of the return value. This is because it suppresses exceptions access the WaitHandle and may return faster than expected.
internal static class CancellationTokenExtensions
{
/// <summary>
/// Wait up to a given duration for a token to be cancelled.
/// Returns true if the token was cancelled within the duration
/// or the underlying cancellation token source has been disposed.
/// </summary>
public static bool WaitForCancellation(this CancellationToken token, TimeSpan duration)
{
WaitHandle handle;
try
{
handle = token.WaitHandle;
}
catch
{
// eg. CancellationTokenSource is disposed
return true;
}
return handle.WaitOne(duration);
}
}
To cancel an asynchronious operation after a certain amount of time whilst still being able to cancel the operation manually use something like the following
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
cts.CancelAfter(5000);
This will cause a cancellation after five seconds. To cancel the operation your self all you have to do is pass the token
into your async method and use the token.ThrowifCancellationRequested()
method, where you have set up an event handler somewhere to fire cts.Cancel()
.
So a full example is:
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
cts.CancelAfter(5000);
// Set up the event handler on some button.
if (cancelSource != null)
{
cancelHandler = delegate
{
Cancel(cts);
};
stopButton.Click -= cancelHandler;
stopButton.Click += cancelHandler;
}
// Now launch the method.
SomeMethodAsync(token);
Where stopButton
is the button you click to cancel the running task
private void Cancel(CancellationTokenSource cts)
{
cts.Cancel();
}
and the method is defined as
SomeMethodAsync(CancellationToken token)
{
Task t = Task.Factory.StartNew(() =>
{
msTimeout = 5000;
Pump(token);
}, token,
TaskCreationOptions.None,
TaskScheduler.Default);
}
Now, to enable you to work the thread but also enable user cancellation, you will need to write a 'pumping' method
int msTimeout;
bool timeLimitReached = false;
private void Pump(CancellationToken token)
{
DateTime now = DateTime.Now;
System.Timer t = new System.Timer(100);
t.Elapsed -= t_Elapsed;
t.Elapsed += t_Elapsed;
t.Start();
while(!timeLimitReached)
{
Thread.Sleep(250);
token.ThrowIfCancellationRequested();
}
}
void t_Elapsed(object sender, ElapsedEventArgs e)
{
TimeSpan elapsed = DateTime.Now - this.readyUpInitialised;
if (elapsed > msTimeout)
{
timeLimitReached = true;
t.Stop();
t.Dispose();
}
}
Note, SomeAsyncMethod
will return right to the caller. To block the caller aswell you will have to move the Task
up in the call hierarchy.