How to "sleep" until timeout or cancellation is requested

32.4k views Asked by At

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'm looking for a solution which works in .NET Framework 4.0+.

I'd like to write:

void MyFunc(CancellationToken ct)
{
    //... 
    // simulate some long lasting operation that should be cancelable 
    Thread.Sleep(TimeSpan.FromMilliseconds(10000), ct); 
}
5

There are 5 answers

8
Frode On BEST ANSWER

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));
}
1
Onur On

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.

4
MoonKnight On

To cancel an asynchronous 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 as well you will have to move the Task up in the call hierarchy.

1
user2864740 On

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 in 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 to access of 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
        {
            /// The source of the token was disposed (already cancelled)
            return true;
        }

        if (handle.WaitOne(duration))
        {
            /// A cancellation occured during the wait
            return true;
        }
        else
        {
            /// No cancellation occured during the wait
            return false;
        }
    }
}
6
Fowl On

Alternatively, I think this is pretty clear:

Task.Delay(waitTimeInMs, cancellationToken).Wait(cancellationToken);