72

睡一定时间但又能被 a 打断的最好方法是IsCancellationRequested什么CancellationToken

我正在寻找适用于 .NET 4.0 的解决方案。

我想写

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

5 回答 5

131

我刚刚在这里写了一篇博客:

CancellationToken 和 Thread.Sleep

简而言之:

var cancelled = token.WaitHandle.WaitOne(TimeSpan.FromSeconds(5));

在您的上下文中:

void MyFunc (CancellationToken ct)
{
   //... 
   // simulate some long lasting operation that should be cancelable 
   var cancelled = ct.WaitHandle.WaitOne(TimeSpan.FromSeconds(10));
}
于 2014-05-13T13:50:49.670 回答
14

或者,我认为这很清楚:

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

于 2016-10-06T00:51:38.860 回答
4

要在一定时间后取消异步操作,同时仍然能够手动取消操作,请使用以下内容

CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
cts.CancelAfter(5000);

这将导致五秒钟后取消。要取消您自己的操作,您只需将token传入您的 async 方法并使用该token.ThrowifCancellationRequested()方法,您已经在某处设置了一个事件处理程序来触发cts.Cancel()

所以一个完整的例子是:

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);

stopButton点击取消正在运行的任务的按钮在哪里

private void Cancel(CancellationTokenSource cts)
{
    cts.Cancel();
}

该方法定义为

SomeMethodAsync(CancellationToken token)
{
    Task t = Task.Factory.StartNew(() => 
        {
            msTimeout = 5000;
            Pump(token);
        }, token,
           TaskCreationOptions.None,
           TaskScheduler.Default);
}

现在,为了使您能够工作线程并启用用户取消,您将需要编写一个“抽水”方法

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();
    }
}

注意,SomeAsyncMethod将权利返回给调用者。要同时阻止呼叫者,您必须Task在呼叫层次结构中向上移动。

于 2013-09-10T09:28:57.353 回答
2

CancellationToken.WaitHandle在CancellationTokenSource 被释放后访问时可能会引发异常:

ObjectDisposedException:已释放 CancellationTokenSource。

在某些情况下,尤其是在手动处理链接的取消源时(应该如此),这可能会很麻烦。

此扩展方法允许“安全取消等待”;但是,它应该与检查和正确标记取消令牌的状态和/或返回值的使用结合使用。这是因为它抑制了对 WaitHandle 的访问的异常,并且返回的速度可能比预期的要快。

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;
        }
    }
}
于 2019-06-21T17:05:55.437 回答
1

到目前为止我发现的最佳解决方案是:

void MyFunc(CancellationToken ct)
{
  //...
  var timedOut = WaitHandle.WaitAny(new[] { ct.WaitHandle }, TimeSpan.FromMilliseconds(2000)) == WaitHandle.WaitTimeout;
  var cancelled = ! timedOut;
}

更新:

迄今为止最好的解决方案是公认的答案

于 2013-09-10T09:24:48.390 回答