gpt4 book ai didi

c# - 顶级请求上的 ConfigureAwait(false)

转载 作者:行者123 更新时间:2023-12-01 21:25:55 25 4
gpt4 key购买 nike

我想弄清楚是否应该在顶级请求上使用 ConfigureAwait(false)。从该主题的某种权威阅读这篇文章:
http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html

......他推荐这样的东西:

public async Task<JsonResult> MyControllerAction(...)
{
try
{
var report = await _adapter.GetReportAsync();
return Json(report, JsonRequestBehavior.AllowGet);
}
catch (Exception ex)
{
return Json("myerror", JsonRequestBehavior.AllowGet); // really slow without configure await
}
}

public async Task<TodaysActivityRawSummary> GetReportAsync()
{
var data = await GetData().ConfigureAwait(false);

return data
}

...它说在除了顶级调用之外的每个等待上使用 ConfigureAwait(false) 。 但是,当这样做时,我的异常需要几秒钟才能返回给调用者,而不是使用它并立即返回。

调用异步方法的 MVC Controller 操作的最佳实践是什么?我应该在 Controller 本身中使用 ConfigureAwait 还是只在使用 awaits 请求数据等的服务调用中使用?如果我不在顶级调用中使用它,等待几秒钟的异常似乎有问题。我不需要 HttpContext 并且我已经看到其他帖子说如果您不需要上下文,请始终使用 ConfigureAwait(false) 。

更新:
我在调用链的某处丢失了 ConfigureAwait(false),这导致异常无法立即返回。然而,问题仍然是关于是否应该在顶层使用 ConfigureAwait(false) 的问题。

最佳答案

它是一个高流量的网站吗?一种可能的解释可能是您遇到了 ThreadPool不使用时饥饿 ConfigureAwait(false) .无 ConfigureAwait(false) , await继续通过 AspNetSynchronizationContext.Post 排队,该实现归结为 this :

Task newTask = _lastScheduledTask.ContinueWith(_ => SafeWrapCallback(action));
_lastScheduledTask = newTask; // the newly-created task is now the last one

在这里, ContinueWith在没有 TaskContinuationOptions.ExecuteSynchronously 的情况下使用(我推测,使延续真正异步并减少低堆栈情况的机会)。因此,它从 ThreadPool 获取一个空线程。继续执行。理论上,它可能恰好与 await 的先行任务所在的线程相同。已经完成,但很可能它会是一个不同的线程。

此时,如果 ASP.NET 线程池耗尽(或必须增长以容纳新的线程请求),您可能会遇到延迟。值得一提的是,线程池由两个子池组成:IOCP 线程和工作线程(查看 thisthis 以获取更多详细信息)。您的 GetReportAsync操作很可能在一个 IOCP 线程子池上完成,它似乎并不饿。 OTOH, ContinueWith继续在工作线程子池上运行,这在您的情况下似乎很饿。

ConfigureAwait(false) 的情况下不会发生这种情况一直使用。在这种情况下,所有 await continuation 将在相应的先行任务已经结束的相同线程上同步运行,无论是 IOCP 还是工作线程。

您可以比较两个场景的线程使用情况,有和没有 ConfigureAwait(false) .我希望这个数字在 ConfigureAwait(false) 时会更大未使用:
catch (Exception ex)
{
Log("Total number of threads in use={0}",
Process.GetCurrentProcess().Threads.Count);
return Json("myerror", JsonRequestBehavior.AllowGet); // really slow without configure await
}

您还可以尝试增加 ASP.NET 线程池的大小(用于诊断目的,而不是最终解决方案),以查看所描述的场景是否确实如此:

<configuration>
<system.web>
<applicationPool
maxConcurrentRequestsPerCPU="6000"
maxConcurrentThreadsPerCPU="0"
requestQueueLimit="6000" />
</system.web>
</configuration>

更新 处理评论:

I realized I was missing a ContinueAwait somewhere in my chain. Now it works fine when throwing an exception even when the top level doesn't use ConfigureAwait(false).



这表明您的代码或正在使用的第 3 方库可能正在使用阻塞结构( Task.ResultTask.WaitWaitHandle.WaitOne ,也许添加了一些超时逻辑)。你找过那些吗?试试 Task.Run来自此更新底部的建议。此外,我仍然会进行线程计数诊断以排除线程池饥饿/口吃。

So are you saying that if I DO use ContinueAwait even at the top level I lose the whole benefit of the async?



不,我不是这么说的。整点 async是为了避免在等待时阻塞线程,无论 ContinueAwait(false) 的附加值如何,该目标都可以实现。 .

我要说的是不使用 ConfigureAwait(false)可能会引入冗余上下文切换(通常意味着线程切换),如果线程池在其容量下工作,这可能是 ASP.NET 中的问题。尽管如此,就服务器可扩展性而言,冗余线程切换仍然优于阻塞线程。

平心而论, using ContinueAwait(false) 也可能导致冗余上下文切换,特别是如果它在整个调用链中的使用不一致。

也就是说, ContinueAwait(false)也经常被误用作为解决由 blocking on asynchronous code 引起的死锁的补救措施.这就是为什么我在上面建议在所有代码库中寻找那些阻塞构造的原因。

However the question still remains as posted as to whether or not ConfigureAwait(false) should be used at the top level.



我希望 Stephen Cleary 能对此进行更好的阐述,这是我的想法。

总会有一些“ super 顶级”代码调用您的顶级代码。例如,对于 UI 应用程序,它是调用 async void 的框架代码。事件处理程序。如果是 ASP.NET,则是异步 Controller 的 BeginExecute .确保在异步任务完成后,延续(如果有)在正确的同步上下文中运行是该 super 顶级代码的责任。这不是您的任务代码的责任。例如,可能根本没有延续,就像一个即发即忘的 async void事件处理程序;你为什么要在这样的处理程序中恢复上下文?

因此,在您的顶级方法中,如果您不关心 await 的上下文继续,请使用 ConfigureAwait(false)你尽快做。

此外,如果您使用的是已知与上下文无关但仍可能使用 ConfigureAwait(false) 的第 3 方库。不一致,你可能想用 Task.Run 结束通话或类似 WithNoContext .你这样做是为了提前从上下文中获取异步调用链:
var report = await Task.Run(() =>
_adapter.GetReportAsync()).ConfigureAwait(false);
return Json(report, JsonRequestBehavior.AllowGet);

这将引入一个额外的线程切换,但如果 ConfigureAwait(false) 可能会为您节省更多的线程切换。内部使用不一致 GetReportAsync或其任何子调用。它还可以作为由调用链中的那些阻塞构造(如果有)引起的潜在死锁的解决方法。

但是请注意,在 ASP.NET 中 HttpContext.Current不是唯一与 AspNetSynchronizationContext 一起流动的静态属性.例如,还有 Thread.CurrentThread.CurrentCulture .确保你真的不关心失去上下文。

更新 解决评论:

For brownie points, maybe you can explain the effects of ConfigureAwait(false)... What context isn't preserved.. Is it just the HttpContext or the local variables of the class object, etc.?


async 的所有局部变量方法在 await 中保留,以及隐含的 this引用 - 按设计。它们实际上被捕获到编译器生成的异步状态机结构中,因此从技术上讲,它们并不驻留在当前线程的堆栈中。在某种程度上,它类似于 C# 委托(delegate)捕获局部变量的方式。事实上,一个 await连续回调本身就是一个传递给 ICriticalNotifyCompletion.UnsafeOnCompleted 的委托(delegate)(由正在等待的对象实现;对于 Task ,它是 TaskAwaiter ;对于 ConfigureAwait ,它是 ConfiguredTaskAwaitable )。

OTOH,大部分 全局状态 (静态/TLS 变量,静态类属性)是 不是 自动流过等待。流动的内容取决于特定的同步上下文。如果没有(或使用 ConfigureAwait(false) 时),唯一保留的全局状态是由 ExecutionContext 流动的状态。 . Microsoft 的 Stephen Toub 对此发表了一篇很棒的文章: "ExecutionContext vs SynchronizationContext" .他提到 SecurityContextThread.CurrentPrincipal ,这对安全至关重要。除此之外,我不知道 ExecutionContext 流过的任何正式记录和完整的全局状态属性列表。 .

您可以查看 ExecutionContext.Capture source了解更多关于究竟是什么流动,但你不应该依赖于这个特定的实现。相反,您始终可以创建自己的全局状态流逻辑,使用类似 Stephen Cleary 的 AsyncLocal 之类的东西。 (或 .NET 4.6 AsyncLocal<T> )。

或者,把它发挥到极致,你也可以放弃 ContinueAwait完全并创建一个自定义等待器,例如像这样 ContinueOnScope .这将允许精确控制要继续的线程/上下文以及要流动的状态。

关于c# - 顶级请求上的 ConfigureAwait(false),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31225972/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com