在任务型异步方法在C#超时模式任务、模式、方法

2023-09-06 10:51:21 作者:悲切

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

内置超时

 公共任务DoStuffAsync(时间跨度超时)
 

此方法是很难实现,因为这是不容易实现的全球的超时时间,整个调用堆栈。例如,一个Web API控制器接收到HTTP请求,并将其称之为 DoStuffAsync ,呼叫者想要的3秒的全局超时。

也就是说,每个内异步方法调用将需要接收的已使用时间的减...

无内置超时

 公共任务DoStuffAsync(的CancellationToken的CancellationToken)

..........

CancellationTokenSource cancellationSource =新CancellationTokenSource();
任务timeoutTask = Task.Delay(3000);

如果(计谋Task.WhenAny(DoStuffAsync(cancellationTokenSource),timeoutTask)== timeoutTask)
{
     cancellationSource.Cancel();

     抛出新TimeoutException异常();
}
 

这似乎是最可靠的,易于实施的图案。第一个来电定义一个全局超时,如果它的超时的,所有待处理操作将被取消。此外,它提供了一个取消标记来直接调用方和内部通话将共享相同的取消标记参考。因此,如果顶呼叫者超时,它将能够取消任何工作线程。

的整个问题

有没有办法,我失踪或任何图案,我在正确的方式,如果我开发使用的API中的没有内置超时的?

解决方案   

有没有办法,我失踪或任何图案,我在正确的方式,如果我开发使用无内置超时的API?

免责声明:

等保测评 SQLServer操作超时

当我们处于取消状态谈工作,我们的意思是,我们取消操作,因为它进入。这可能不是这里的情况,当我们谈论取消,因为我们只是放弃了任务,如果在指定时间间隔后完成。这是讨论在下面斯蒂芬Toubs文章,为什么首创置业不提供取消正在进行的操作的开箱即用的功能范围。

我看到时下常见的方法是的没有内置的的方法和一个我发现自己使用主要是为了实现消除机构。它是两者的肯定的更容易,而使最高帧来负责取消,而通过内框架的取消标记。如果你发现自己重复这种模式,您可以用已知的 WithCancellation 扩展方法:

 公共静态异步任务< T> WithCancellation< T>(
        这个任务< T>任务的CancellationToken的CancellationToken)
    {
        VAR cancellationCompletionSource =新TaskCompletionSource<布尔>();

        使用(cancellationToken.Register(()=> cancellationCompletionSource.TrySetResult(真)))
        {
            如果(任务!=等待Task.WhenAny(任务,cancellationCompletionSource.Task))
            {
                抛出新OperationCanceledException(的CancellationToken);
            }
        }

        返回等待任务;
    }
 

这是从斯蒂芬Toubs 如何取消不取消异步操作?的这是不完全察觉到你在问什么,但绝对是值得一读。

借助的任务取消文档 去指定任务取消的方式有两种:

  

您可以通过使用这些选项之一终止操作:

        

通过简单地从委托返回。在许多情况下,这是足够的;然而,一个任务实例已被取消以这种方式转移到TaskStatus.RanToCompletion状态,而不是TaskStatus.Canceled状态。

  

通过投掷OperationCanceledException,并通过它这是要求取消标记。在preferred方式做到这一点是使用ThrowIfCancellationRequested方法。这项任务被取消以这种方式转换到取消状态,调用code可以用它来验证任务响应其取消请求

  

修改

至于你关注使用时间跨度指定所需的时间间隔,使用的 CancellationTokenSource 构造过载,这需要时间跨度参数:

  VAR CTS =新CancellationTokenSource(TimeSpan.FromSeconds(3));

变种任务= Task.Run(()=> DoStuff())WithCancellation(cts.Token)。
 

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.

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?

解决方案

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?

Disclaimer:

When we talk about a Task in a cancelled state, we mean that we cancel the operation as it proceeds. 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.

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:

    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 Stephan Toubs How do I cancel non-cancelable async operations? which isn't exactly spot on to what you're asking, but is definitely worth a read.

The Task Cancellation docs go on to specify two ways of task cancellation:

You can terminate the operation by using one of these options:

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.

By throwing a OperationCanceledException and passing it the token on which cancellation was requested. The preferred way to do this is to use the ThrowIfCancellationRequested method. 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

Edit

As for you concern with using a TimeSpan to specify the desired interval, use the overload of CancellationTokenSource constructor which takes a TimeSpan parameter:

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

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