C#中基于任务的异步方法的超时模式

据我所知,有两种可能的模式来实现基于任务的异步方法的超时:

内置超时

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

这似乎是最可靠和易于实现的模式。 第一个调用者定义全局超时,如果超时 ,则将取消所有挂起的操作。 此外,它为直接调用者提供取消令牌,内部调用将共享相同的取消令牌引用。 因此,如果最高调用者超时,它将能够取消任何工作线程。

整个问题

有没有我缺少的模式,或者如果我使用no内置超时开发API,我是否正确?

有没有我缺少的模式,或者如果我使用no内置超时开发API,我是否正确?

免责声明:

当我们谈论处于取消状态的Task时,我们的意思是我们在进行时取消操作。 当我们谈论取消时,这可能不是这种情况,因为如果在指定的间隔之后完成任务,我们就会丢弃该任务。 在下面的Stephan Toubs文章中讨论了为什么BCL不提供取消正在进行的操作的OOTB特征的原因。


我现在看到的常见方法是无内置方法,我发现自己主要用于实现取消机制。 绝对是两者中更容易的,留下最高帧负责取消,同时传递内部帧取消令牌。 如果您发现自己重复此模式,则可以使用已知的WithCancellation扩展方法:

 public static async Task WithCancellation( this Task task, CancellationToken cancellationToken) { var cancellationCompletionSource = new TaskCompletionSource(); using (cancellationToken.Register(() => cancellationCompletionSource.TrySetResult(true))) { if (task != await Task.WhenAny(task, cancellationCompletionSource.Task)) { throw new OperationCanceledException(cancellationToken); } } return await task; } 

这是来自Stephan Toubs 如何取消不可取消的异步操作? 这并不完全符合您的要求,但绝对值得一读。

任务取消文档继续指定两种任务取消方式:

您可以使用以下选项之一终止操作:

  1. 只需从代表处返回即可。 在许多情况下,这已足够; 但是,以这种方式取消的任务实例将转换为TaskStatus.RanToCompletion状态,而不是TaskStatus.Canceled状态。

  2. 通过抛出OperationCanceledException并向其传递请求取消的令牌。 执行此操作的首选方法是使用ThrowIfCancellationRequested方法。 以这种方式取消的任务转换为Canceled状态,调用代码可以使用该状态来validation任务是否响应了其取消请求

编辑

至于使用TimeSpan指定所需间隔的问题,请使用带有TimeSpan参数的CancellationTokenSource构造函数的重载 :

 var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3)); var task = Task.Run(() => DoStuff()).WithCancellation(cts.Token); 

虽然您可以重复使用WithCancellation取消和超时,但我认为这对您的需求来说太过分了。

async操作超时的更简单,更清晰的解决方案是使用Task.WhenAny await实际操作和超时任务。 如果超时任务首先完成,则会超时。 否则,操作成功完成:

 public static async Task WithTimeout(this Task 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. } 

如果您不想抛出exception(就像我一样)它甚至更简单,只需返回默认值:

 public static Task WithTimeout(this Task task, TimeSpan timeout) { var timeoutTask = Task.Delay(timeout).ContinueWith(_ => default(TResult), TaskContinuationOptions.ExecuteSynchronously); return Task.WhenAny(task, timeoutTask).Unwrap(); }