如何在 .NET 4.0 中“休眠”,直到请求超时或取消
本文关键字:请求 超时 取消 休眠 NET | 更新日期: 2023-09-27 18:30:43
睡一定时间,但能被CancellationToken
的IsCancellationRequested
打断的最佳方法是什么?
我正在寻找一种适用于 .NET 4.0 的解决方案。
我想写
void MyFunc (CancellationToken ct)
{
//...
// simulate some long lasting operation that should be cancelable
Thread.Sleep(TimeSpan.FromMilliseconds(10000), ct);
}
我只是在这里写了博客:
CancelToken and 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));
}
或者,我认为这很清楚:
Task.Delay(waitTimeInMs, cancellationToken).Wait(cancellationToken);
要在一定时间后取消异步操作,同时仍能够手动取消操作,请使用如下所示的内容
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
cts.CancelAfter(5000);
这将导致五秒钟后取消。要取消操作,您所要做的就是将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
。
CancelToken.WaitHandle 在 CancelTokenSource 被释放后访问时可能会引发异常:
ObjectDisposedException:CancelTokenSource 已被处置。
在某些情况下,尤其是当链接的取消源被手动处理时(它们应该如此),这可能会令人讨厌。
此扩展方法允许"安全取消等待";但是,它应与取消令牌的状态和/或返回值的用法的检查和正确标记结合使用。这是因为它禁止访问 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;
}
}
}
到目前为止,我找到的最佳解决方案是:
void MyFunc(CancellationToken ct)
{
//...
var timedOut = WaitHandle.WaitAny(new[] { ct.WaitHandle }, TimeSpan.FromMilliseconds(2000)) == WaitHandle.WaitTimeout;
var cancelled = ! timedOut;
}
更新:
到目前为止,最好的解决方案是公认的答案。