How to “sleep” until timeout or cancellation is requested in .NET 4.0

后端 未结 5 1964
终归单人心
终归单人心 2020-12-13 12:24

What\'s the best way to sleep a certain amount of time, but be able to be interrupted by a IsCancellationRequested from a CancellationToken?

<
相关标签:
5条回答
  • 2020-12-13 13:06

    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));
    }
    
    0 讨论(0)
  • 2020-12-13 13:07

    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.

    0 讨论(0)
  • 2020-12-13 13:10

    Alternatively, I think this is pretty clear:

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

    0 讨论(0)
  • 2020-12-13 13:13

    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);
        }
    }
    
    0 讨论(0)
  • 2020-12-13 13:26

    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.

    0 讨论(0)
提交回复
热议问题