如何“睡眠”,直到在.NET 4.0中请求超时或取消

什么是最好的方式睡一定的时间,但能够被一个从 CancellationTokenIsCancellationRequested打断?

我正在寻找一个在.NET 4.0中工作的解决方案。

我想写作

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

到目前为止,我找到的最佳解决方案是:

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

更新:

到目前为止最好的解决方案是 接受的答案

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

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

这将在5秒钟后导致取消。要取消自己的操作,您所要做的就是将 token传递到您的异步方法中,并使用 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在调用层次结构中向上移动。

我刚在博客上写道:

取消令牌和线程。睡眠

简而言之:

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

或者,我认为这很清楚:

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

在释放 CcellationTokenSource 之后访问时,取消令牌,等待句柄可以引发异常:

ObjectDisposedException: 已经释放了 CcellationTokenSource。

在某些情况下,特别是当 连接的取消源正在被人工处理时(它们应该是这样的) ,这可能是一个麻烦。

这个扩展方法允许“安全取消等待”; 但是,它应该与对取消令牌的状态和/或返回值的使用的检查和正确标记结合使用。这是因为它抑制访问 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;
}
}
}

基于我非常欣赏的 接受的答案,这里有一个我在任何地方都会使用的扩展方法:

public static void Sleep(this TimeSpan ts, CancellationToken token)
{
if (ts == TimeSpan.Zero)
return;
token.WaitHandle.WaitOne(ts);
}