究竟什么时候同步延续是危险的?

rel*_*dom 3 .net c# asynchronous async-await .net-core

在一篇博客文章中,微软的 Sergey Tepliakov 说:

在创建 TaskCompletionSource 实例时,您应该始终提供 TaskCreationOptions.RunContinuationsAsynchronously。

但是在那篇文章中,除非我误解了它,否则他还说所有的await延续基本上都与TaskCompletionSourcewithout 的行为方式相同TaskCreationOptions.RunContinuationsAsynchronously。因此,这意味着async并且await本质上也是危险的,不应使用,除非await Task.Yield()也使用。

(编辑:我确实误解了它。他说await同步继续是导致问题SetResult行为的原因await Task.Yield()如果无法在源头控制任务创建,建议将其作为客户端解决方法。除非我再次误解了某些东西。)

我的结论是,一定有特定的情况使同步延续变得危险,而且显然这些情况对于TaskCompletionSource用例来说很常见。那些危险的环境是什么?

Mar*_*ell 5

线程盗窃,基本上。

这里的一个很好的例子是一个客户端库,它通过网络与某个服务器对话,该服务器在同一连接(例如 http/2,或 RESP)上实现为一系列帧。无论出于何种原因,想象对库的每次调用都会返回一个Task<T>(对于某些<T>),它是由TaskCompletionSource<T>正在等待来自网络服务器的结果的 a 提供的。

现在您要编写读取循环,该循环将来自服务器的响应(可能来自套接字、流或管道 API)出列,解析相应的TaskCompletionSource<T>(这可能意味着“队列中的下一个”,或者可能意味着“使用响应中存在的唯一关联键/令牌”)。

所以你有效地拥有:

while (communicationIsAlive)
{
    var result = await ParseNextResultAsync(); // next message from the server
    TaskCompletionSource<Foo> tcs = TryResolveCorrespondingPendingRequest(result);
    tcs?.TrySetResult(result); // or possibly TrySetException, etc
}
Run Code Online (Sandbox Code Playgroud)

现在; 想象一下,您RunContinuationsAsynchronously在创建TaskCompletionSource<T>. 当您这样做时TrySetResult,延续将在当前线程上执行。这意味着await某些任意客户端代码(可以执行任何操作)现在已经中断了您的 IO 读取循环,并且在该线程放弃该线程之前不会处理其他结果

UsingRunContinuationsAsynchronously允许您使用TrySetResult(et al) 而不必担心您的线程被盗。

(更糟糕的是:如果您将此与对同一资源的后续同步异步调用相结合,可能会导致硬死锁)

存在此标志之前的相关问题:如何防止任务上的同步继续?.

  • @relatively_random“结论” - 实际上,是的;但是:它不一定是“无限期”;如果读取循环是限制因素,那么即使是“一会儿”也可能对性能造成巨大损害。关于“太激进” - 好吧,一切都是主观的,但是我们如何将其软化为“使用`TaskCreationOptions.RunContinuationsAsynchronously`,除非你能准确解释它的作用以及为什么你在你的情况下不希望它”:)建议将其作为默认选项是更安全的选择,并且几乎总是会导致成功的情况 (2认同)