C# C语言中基于任务的异步方法超时模式#
据我所知,有两种可能的模式可以实现对基于任务的异步方法的超时: 内置超时 这种方法更难实现,因为为整个调用堆栈实现全局超时并不容易。例如,Web API控制器接收HTTP请求并调用C# C语言中基于任务的异步方法超时模式#,c#,.net,design-patterns,asynchronous,async-await,C#,.net,Design Patterns,Asynchronous,Async Await,据我所知,有两种可能的模式可以实现对基于任务的异步方法的超时: 内置超时 这种方法更难实现,因为为整个调用堆栈实现全局超时并不容易。例如,Web API控制器接收HTTP请求并调用DoStuffAsync,调用方希望全局超时为3秒 也就是说,每个内部异步方法调用都需要接收已使用时间的减法 没有内置超时 这似乎是最可靠、最容易实现的模式。第一个调用方定义了一个全局超时,如果它超时,所有挂起的操作都将被取消。此外,它还向直接呼叫方提供取消令牌,内部呼叫将共享相同的取消令牌引用。因此,如果顶级调用程序
DoStuffAsync
,调用方希望全局超时为3秒
也就是说,每个内部异步方法调用都需要接收已使用时间的减法
没有内置超时
这似乎是最可靠、最容易实现的模式。第一个调用方定义了一个全局超时,如果它超时,所有挂起的操作都将被取消。此外,它还向直接呼叫方提供取消令牌,内部呼叫将共享相同的取消令牌引用。因此,如果顶级调用程序超时,它将能够取消任何工作线程
整个问题
如果我使用无内置超时开发API,是否有任何模式我遗漏了,或者我的方式正确
如果我使用无内置超时开发API,是否有任何模式我遗漏了,或者我的方式正确
免责声明:
当我们谈论处于取消状态的任务时,我们的意思是在操作进行时取消操作。当我们谈论取消时,这里可能不是这种情况,因为如果任务在指定的时间间隔后完成,我们只需放弃它。下面的Stephan Toubs文章对此进行了一定程度的讨论,解释了为什么BCL不提供取消正在进行的操作的OOTB功能
我现在看到的常用方法是无内置方法,我发现自己主要使用这种方法来实现取消机制。这肯定是两种方法中比较容易的一种,让最高帧负责取消,同时将取消令牌传递给内部帧。如果您发现自己重复此模式,可以使用已知的with cancellation
扩展方法:
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;
}
虽然您可以在取消和超时时重复使用with cancellation
,但我认为这对于您所需要的功能来说是一种过度使用
对于异步
操作超时,一个更简单、更清晰的解决方案是使用任务等待实际操作和超时任务。如果超时任务先完成,则为自己设置了一个超时。否则,操作将成功完成:
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();
}
如果您不希望抛出异常(如我所做的),则更简单,只需返回默认值:
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();
}
带超时的公共静态任务(此任务任务,TimeSpan超时)
{
var timeoutTask=Task.Delay(timeout).ContinueWith(=>default(TResult),TaskContinuationOptions.ExecuteSynchronously);
返回Task.WhenAny(Task,timeoutTask).Unwrap();
}
这是可行的,尽管“取消”意味着操作没有(也不会)完成,但此代码不一定正确。引用一句话,BCL中没有包括这一点“因为担心它会成为一个拐杖,在没有考虑后果的情况下使用得太快,这是微妙的。”我认为OP理解这里讨论的“取消”的语义。虽然我会修改我的答案,让它更清楚。@YuvalItzchakov完美!谢谢有没有一种方法可以为多个任务(使用Task.whalll
)实现这一点?我已经尝试过了,但是抛出一个任务的异常会导致其他任务也被中止。PS-我需要使用等待
而不是任务。运行
,因为我需要任务中的HttpContext
。@YuvalItzchakov您在上面提供并在2012年编写的Stephen Taub文章()在2019年仍然适用吗?我们现在还有更好的办法吗?很抱歉,我只是问一下,因为我读到了很多关于使用NetworkStream的ReadAsync和WriteAsync时的抱怨,很多抱怨都是从那时开始的,再加上微软关于这个烂家伙的文档。事实上,这就是我在问问题之前所做的,没有像你的扩展方法那样的泛化,但你是对的,大部分情况下这应该是可行的。顺便说一句,我不确定控制异常流是否是个好主意。我不会那么做的。我将返回一个带有布尔值的元组,以指定操作是否已完成not@Mat使用异常是.Net库的操作方式。当取消时,它也会抛出。我确实希望避免这些异常,但是我返回默认值(添加到答案中),而不是元组。另一个选项是使用TryX(out result)
范例。@i3Arn在任何地方解释如何实现NetworkStream的ReadAsync和WriteAsync取消。我在哪里都找不到,根据这篇帖子@cd491415,我一直处于死锁状态。你是什么意思?您有自己的网络流
?一个是.NET不会覆盖WriteAsync
/ReadAsync
,因此您可以得到流
实现,如果在操作开始之前取消了CancellationToken
,那么它只会查看。所以,这些操作实际上是不可取消的。@i3arnon不,我说的是来自.NET的NetworkStream。我正试图找出如何正确使用WriteAsync和ReadAsync,因为我无法在Xamarin.Forms的主线程上使用网络。所以,我必须使用WriteAsync来获取远程的信息,然后如果有响应,使用ReadAsync来读取我得到的响应。
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3));
var task = Task.Run(() => DoStuff()).WithCancellation(cts.Token);
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();
}