简体   繁体   English

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

[英]Timeout pattern on task-based asynchronous method in C#

As far as I know, there're two possible patterns to implement a timeout to task-based asynchronous methods:据我所知,有两种可能的模式可以实现基于任务的异步方法的超时:

Built-in timeout内置超时

public Task DoStuffAsync(TimeSpan timeout)

This approach is harder to implement because it's not easy to implement a global timeout for the entire call stack.这种方法更难实现,因为要为整个调用堆栈实现全局超时并不容易。 For example, a Web API controller receives an HTTP request and it calls DoStuffAsync , and the caller wants a global timeout of 3 seconds.例如,Web API 控制器接收到一个 HTTP 请求并调用DoStuffAsync ,并且调用者想要 3 秒的全局超时。

That is, each inner async method call will need to receive the subtract of already used time...也就是说,每个内部异步方法调用都需要接收已使用时间的减法...

No built-in timeout没有内置超时

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

This seems to be the most reliable and easy to implement pattern.这似乎是最可靠和最容易实现的模式。 The first caller defines a global timeout, and if it time outs , all pending operations will be cancelled.第一个调用者定义了一个全局超时,如果超时,将取消所有挂起的操作。 Also, it provides a cancellation token to the immediate caller and inner calls will share the same cancellation token reference.此外,它为直接调用者提供取消令牌,内部调用将共享相同的取消令牌引用。 Thus, if the top caller time outs, it will be able to cancel any working thread.因此,如果顶级调用者超时,它将能够取消任何工作线程。

The whole question整个问题

Is there any pattern that I'm missing or, am I in the right way if I develop APIs using the no built-in timeout ?是否有任何我遗漏的模式,或者,如果我使用无内置超时开发 API,我的方式是否正确?

While you can reuse WithCancellation for both cancellations and timeouts I think it's an overkill for what you need.虽然您可以将WithCancellation用于取消和超时,但我认为这对于您的需求来说太过分了。

A simpler and clearer solution for an async operation timeout would be to await both the actual operation and a timeout task using Task.WhenAny . async操作超时的一个更简单、更清晰的解决方案是使用Task.WhenAny await实际操作和超时任务。 If the timeout task completes first, you got yourself a timeout.如果超时任务首先完成,您就会超时。 Otherwise, the operation completed successfully:否则,操作成功完成:

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

Usage:用法:

try
{
    await DoStuffAsync().WithTimeout(TimeSpan.FromSeconds(5));
}
catch (TimeoutException)
{
    // Handle timeout.
}

If you prefer to not throw an exception (as I do) it's even simpler, just return the default value:如果您不想抛出异常(就像我一样),那就更简单了,只需返回默认值:

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

Is there any pattern that I'm missing or, am I in the right way if I develop APIs using the no built-in timeout?是否有任何我遗漏的模式,或者,如果我使用无内置超时开发 API,我的方式是否正确?

Disclaimer:免责声明:

When we talk about a Task in a cancelled state, we mean that we cancel the operation as it proceeds.当我们谈论处于取消状态的Task时,我们的意思是我们在操作进行时取消操作。 This might not be the case here when we talk about cancellation, as we simply discard the task if it completed after the specified interval.当我们谈论取消时,这可能不是这种情况,因为如果任务在指定的时间间隔后完成,我们就会简单地丢弃它。 This is discussed to extent in Stephan Toubs article below as to why the BCL does not provide OOTB features of cancelling an ongoing operation.这在下面的 Stephan Toubs 文章中讨论了为什么 BCL 不提供取消正在进行的操作的 OOTB 功能。


The common approach i see nowadays is the no build-in approach and the one i find myself using mostly to implement a cancelling mechanism.我现在看到的常见方法是无内置方法,我发现自己主要使用的方法是实现取消机制。 It is definitely the easier of the two, leaving the highest frame to be in charge of cancellation while passing the inner frames the cancellation token.这绝对是两者中更容易的,让最高帧负责取消,同时将取消令牌传递给内部帧。 If you find yourself repeating this pattern, you can use the known WithCancellation extension method:如果您发现自己在重复此模式,则可以使用已知的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;
}

This is from Stephen Toub's How do I cancel non-cancelable async operations?这是来自 Stephen Toub 的如何取消不可取消的异步操作? which isn't exactly spot on to what you're asking, but is definitely worth a read.这并不完全符合您的要求,但绝对值得一读。

TheTask Cancellation docs go on to specify two ways of task cancellation: 任务取消文档继续指定两种任务取消方式:

You can terminate the operation by using one of these options:您可以使用以下选项之一终止操作:

  1. By simply returning from the delegate.通过简单地从委托返回。 In many scenarios this is sufficient;在许多情况下,这已经足够了; however, a task instance that is canceled in this way transitions to the TaskStatus.RanToCompletion state, not to the TaskStatus.Canceled state.但是,以这种方式取消的任务实例会转换到 TaskStatus.RanToCompletion 状态,而不是 TaskStatus.Canceled 状态。

  2. By throwing a OperationCanceledException and passing it the token on which cancellation was requested.通过抛出 OperationCanceledException 并将请求取消的令牌传递给它。 The preferred way to do this is to use the ThrowIfCancellationRequested method.执行此操作的首选方法是使用 ThrowIfCancellationRequested 方法。 A task that is canceled in this way transitions to the Canceled state, which the calling code can use to verify that the task responded to its cancellation request以这种方式取消的任务转换为 Canceled 状态,调用代码可以使用该状态来验证任务是否响应了其取消请求

Edit编辑

As for you concern with using a TimeSpan to specify the desired interval, use theoverload of CancellationTokenSource constructor which takes a TimeSpan parameter:至于您担心使用TimeSpan来指定所需的间隔,请使用带有TimeSpan参数CancellationTokenSource构造函数重载

var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3));

var task = Task.Run(() => DoStuff()).WithCancellation(cts.Token);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 在.NET 4.0和C#4.0中使用基于任务的异步模式(TAP) - Use Task-based Asynchronous Pattern (TAP) with .NET 4.0 and C# 4.0 Telerik中基于任务的异步模式支持 - Task-based Asynchronous Pattern support in Telerik 具有基于任务的异步模式的UDP侦听器 - UDP listener with task-based asynchronous pattern 这是实现基于任务的异步方法的正确方法吗? - Is this correct way to implement task-based asynchronous method? 了解基于任务的异步模式C# - Understanding Task based asynchronous pattern C# 从异步编程模型(APM)迁移到基于任务的异步模式(TAP) - Moving from Asynchronous Programming Model (APM) to Task-based Asynchronous Pattern (TAP) 在双工WCF服务的客户端中使用基于任务的异步模式时出错 - Error when I use the Task-based Asynchronous Pattern in the client of duplex WCF service 通过基于任务的异步模式 (TAP) 接触的 Windows 服务作业监视器 - Windows Service Job Monitor via Touch of Task-based Asynchronous Pattern (TAP) Oracle Client与基于任务的异步模式(异步/等待) - Oracle Client vs. Task-based Asynchronous Pattern (async/await) System.NotSupportedException:&#39;不支持在基于任务的异步方法上使用&#39;System.Threading.CancellationToken&#39;。&#39; - System.NotSupportedException: 'The use of 'System.Threading.CancellationToken' on the task-based asynchronous method is not supported.'
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM