76

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 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); 
}
g t
  • 7,287
  • 7
  • 50
  • 85
Onur
  • 5,017
  • 5
  • 38
  • 54

5 Answers5

141

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));
}
AlexH
  • 2,650
  • 1
  • 27
  • 35
Frode
  • 3,325
  • 1
  • 22
  • 32
  • I prefer `token.WaitHandle.WaitOne(TimeSpan.FromMilliseconds(5000));` but it in essence this looks like the most clean solution. – Onur Jul 22 '14 at 10:58
  • 4
    Onur then you could do it just like that `token.WaitHandle.WaitOne(5000)` – Jakoss Dec 05 '16 at 09:30
  • 5
    Hower, the documentation suggest to avoid using the WaitHandle, unless necessary: "Accessing this property causes a WaitHandle to be instantiated. It is preferable to only use this property when necessary, and to then dispose the associated CancellationTokenSource instance at the earliest opportunity (disposing the source will dispose of this allocated handle)." https://msdn.microsoft.com/EN-US/library/dd321769(v=VS.100,d=hv.2).aspx – dennis_ler Feb 20 '18 at 15:53
  • 4
    Warning/Note: accessing `ct.WaitHandle` throws an exception if the underlying CancellationTokenSource is disposed. Easy enough to address with an extension method.. `ct.WaitForCancellation(timespan)` which can return immediately if access throws an exception. Should be used in conjunction with cancellation token checks as could lead to misleading 'no wait' times. – user2864740 Jun 21 '19 at 16:58
  • @Frode, Can you please add the code for declaring `token` and using it ? Thanks –  May 13 '20 at 10:26
  • 1
    @sodjsn26fr, have a look at the CancellationTokenSource class. It exposes a Token property. Thats your token. Create a new CancellationTokenSource() in a parent scope. Pass its Token property to wherever you want to use it, like in a loop somewhere. If the tokenSource.Cancel() method is called then your Token will be signalled for cancellation – Frode May 15 '20 at 20:29
  • 1
    @sodjsn26fr, another thing. You get your token "for free" if you add it as the last parameter in Controller-methods (WebAPI). This token can be used to cancel server-requests on the server – Frode May 15 '20 at 20:32
  • 1
    @Frode, I read your blog with attention and CancellationTokenSource Microsoft help, but I think it would save time for others if the declaration would have been added. Regards. –  May 15 '20 at 20:47
15

Alternatively, I think this is pretty clear:

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

Bouke
  • 11,768
  • 7
  • 68
  • 102
Fowl
  • 4,940
  • 2
  • 26
  • 43
  • 1
    It's not C#4.0 but C#4.5. – Onur Oct 07 '16 at 20:39
  • 3
    The answer from @Frode is better. The reason? It's all too easy to omit the "Wait();" at the end, there are no compile errors, and this doesn't introduce any delay at all! Been burnt by this enough that I simply prefer to avoid this syntax altogether. – Contango May 12 '17 at 14:18
  • 1
    I think you need to handle exceptions as documented at https://msdn.microsoft.com/en-us/library/dd321315%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396 – granadaCoder Dec 06 '17 at 18:04
  • Example code: try { System.Threading.Tasks.Task.Delay(waitTimeInMs, cancellationToken).Wait(); } catch (OperationCanceledException) { } catch (AggregateException aex) { if (null != aex.InnerExceptions) { if (aex.InnerExceptions.Any(inn => inn.GetType() != typeof(System.Threading.Tasks.TaskCanceledException))) { throw; } } } – granadaCoder Dec 06 '17 at 18:05
  • 2
    Is it truly necessary to pass in the CancellationToken to both the delay and wait method call? Wait wont continue to block if the token given to the delay method has cancelled. – Francois du Plessis Oct 26 '21 at 11:39
  • I would also like to know about passing the CancellationToken to both methods. Also, if it's not necessary in the call to Wait, can this be simplified down to "await Task.Delay(waitTimeInMs, cancellationToken);" – Novox Jul 12 '22 at 20:31
4

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.

MoonKnight
  • 23,214
  • 40
  • 145
  • 277
  • 2
    Thanks for your very detailed answer. But I don't think your presented solution is what I was looking for. I want to determine the sleep amount _in_ the function not the place where the function is called! The whole point is to mock some long lasting operation. And how log this takes is not of concern to the calling site. – Onur Sep 10 '13 at 09:40
  • See edit. Also, see [here](http://stackoverflow.com/questions/9719003/spinwait-vs-sleep-waiting-which-one-to-use/9719357#9719357) for difference between `SpinWait` and `Sleep`. – MoonKnight Sep 10 '13 at 09:43
  • I see two problems with your solution: 1.) It doesn't block execution but returns immediately. This is something a "t.Wait();" could solve. 2.) It can only be cancelled _after_ waiting the amount of time, which is quite pointless since I'm finished already anyway. – Onur Sep 10 '13 at 09:52
  • Your solution might work but it looks quite more complicated than the best solution I found so far: http://stackoverflow.com/a/18715183/1254743 – Onur Sep 10 '13 at 10:38
2

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;
        }
    }
}
Nicolas VERHELST
  • 398
  • 3
  • 13
user2864740
  • 60,010
  • 15
  • 145
  • 220
1

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.

Community
  • 1
  • 1
Onur
  • 5,017
  • 5
  • 38
  • 54