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

C# C语言中基于任务的异步方法超时模式#,c#,.net,design-patterns,asynchronous,async-await,C#,.net,Design Patterns,Asynchronous,Async Await,据我所知,有两种可能的模式可以实现对基于任务的异步方法的超时: 内置超时 这种方法更难实现,因为为整个调用堆栈实现全局超时并不容易。例如,Web API控制器接收HTTP请求并调用DoStuffAsync,调用方希望全局超时为3秒 也就是说,每个内部异步方法调用都需要接收已使用时间的减法 没有内置超时 这似乎是最可靠、最容易实现的模式。第一个调用方定义了一个全局超时,如果它超时,所有挂起的操作都将被取消。此外,它还向直接呼叫方提供取消令牌,内部呼叫将共享相同的取消令牌引用。因此,如果顶级调用程序

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

内置超时 这种方法更难实现,因为为整个调用堆栈实现全局超时并不容易。例如,Web API控制器接收HTTP请求并调用
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();
}