据我所知,有两种可能的模式可以实现基于任务的异步方法的超时:
public Task DoStuffAsync(TimeSpan timeout)
这种方法难以实现,因为对整个调用堆栈实现全局超时并不容易。例如,Web API控制器接收HTTP请求并调用DoStuffAsync
,并且调用者希望全局超时为3秒。
也就是说,每个内部异步方法调用都需要接收已经使用过的时间的减法...
public Task DoStuffAsync(CancellationToken cancellationToken)
..........
CancellationTokenSource cancellationSource = new CancellationTokenSource();
Task timeoutTask = Task.Delay(3000);
if(await Task.WhenAny(DoStuffAsync(cancellationTokenSource), timeoutTask) == timeoutTask)
{
cancellationSource.Cancel();
throw new TimeoutException();
}
这似乎是最可靠和易于实现的模式。第一个调用者定义全局超时,如果超时,则所有挂起的操作都将被取消。此外,它为直接调用者提供取消令牌,内部调用将共享相同的取消令牌引用。因此,如果最大调用者超时,它将能够取消任何工作线程。
我是否缺少任何模式,或者如果我使用无内置超时开发API,我是否正确?
答案 0 :(得分:13)
虽然可以重复使用WithCancellation
取消和超时,但我认为这对你所需要的东西来说太过分了。
async
操作超时的更简单,更清晰的解决方案是await
实际操作和使用Task.WhenAny
的超时任务。如果超时任务首先完成,则会超时。否则,操作成功完成:
public static async Task<TResult> WithTimeout<TResult>(this Task<TResult> task, TimeSpan timeout)
{
if (task == await Task.WhenAny(task, Task.Delay(timeout)))
{
return await task;
}
throw new TimeoutException();
}
用法:
try
{
await DoStuffAsync().WithTimeout(TimeSpan.FromSeconds(5));
}
catch (TimeoutException)
{
// Handle timeout.
}
如果您不想抛出异常(就像我一样),它甚至更简单,只需返回默认值:
public static Task<TResult> WithTimeout<TResult>(this Task<TResult> task, TimeSpan timeout)
{
var timeoutTask = Task.Delay(timeout).ContinueWith(_ => default(TResult), TaskContinuationOptions.ExecuteSynchronously);
return Task.WhenAny(task, timeoutTask).Unwrap();
}
答案 1 :(得分:11)
我是否缺少任何模式,或者如果我使用no内置超时开发API,我是否正确?
<强>声明:强>
当我们在取消状态中谈论Task
时,我们的意思是我们在进行时取消操作。当我们谈论取消时,这可能不是这种情况,因为如果在指定的间隔之后完成任务,我们就会丢弃任务。在下面的Stephan Toubs文章中讨论了为什么BCL不提供取消正在进行的操作的OOTB功能的原因。
我现在看到的常见方法是 no build-in 方法,我发现自己主要用于实现取消机制。绝对是两者中更容易的,留下最高帧负责取消,同时传递内部帧取消令牌。如果您发现自己重复此模式,则可以使用已知的WithCancellation
扩展方法:
public static async Task<T> WithCancellation<T>(
this Task<T> task, CancellationToken cancellationToken)
{
var cancellationCompletionSource = new TaskCompletionSource<bool>();
using (cancellationToken.Register(() => cancellationCompletionSource.TrySetResult(true)))
{
if (task != await Task.WhenAny(task, cancellationCompletionSource.Task))
{
throw new OperationCanceledException(cancellationToken);
}
}
return await task;
}
这是来自Stephan Toubs How do I cancel non-cancelable async operations?,它并不完全符合您的要求,但绝对值得一读。
Task Cancellation docs继续指定两种取消任务的方式:
您可以使用以下选项之一终止操作:
只需从代表返回即可。在许多情况下,这已足够;但是,以这种方式取消的任务实例将转换为TaskStatus.RanToCompletion状态,而不是TaskStatus.Canceled状态。
- 醇>
通过抛出OperationCanceledException并将请求取消的令牌传递给它。执行此操作的首选方法是使用ThrowIfCancellationRequested方法。以这种方式取消的任务转换为Canceled状态,调用代码可以使用该状态验证任务是否响应其取消请求
修改强>
至于您关注使用TimeSpan
指定所需的时间间隔,请使用带有TimeSpan
参数的overload of CancellationTokenSource
constructor:
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3));
var task = Task.Run(() => DoStuff()).WithCancellation(cts.Token);