为所有服务器端代码调用ConfigureAwait的最佳实践

时间:2012-11-21 08:24:49

标签: c# asp.net-web-api task-parallel-library async-await

当你有服务器端代码(即一些ApiController)并且你的函数是异步的 - 所以它们返回Task<SomeObject> - 你认为最好的做法就是等待你调用的函数{{ 1}}?

我已经读过它更高效,因为它不必将线程上下文切换回原始线程上下文。但是,使用ASP.NET Web Api,如果您的请求是在一个线程上进行的,并且等待某个函数并调用ConfigureAwait(false),当您返回{的最终结果时,可能会将您置于不同的线程上{1}}功能。

我在下面输入了一个我正在谈论的例子:

ConfigureAwait(false)

4 个答案:

答案 0 :(得分:520)

更新: ASP.NET Core does not have a SynchronizationContext。如果您使用的是ASP.NET Core,则无论是否使用ConfigureAwait(false)都无关紧要。

对于ASP.NET“完整”或“经典”或其他任何内容,此答案的其余部分仍然适用。

原帖(非核心ASP.NET):

This video by the ASP.NET team has the best information on using async on ASP.NET.

  

我已经读过它性能更高,因为它不必将线程上下文切换回原始线程上下文。

UI应用程序也是如此,其中只有一个UI线程需要“同步”回来。

在ASP.NET中,情况有点复杂。当async方法恢复执行时,它会从ASP.NET线程池中获取一个线程。如果使用ConfigureAwait(false)禁用上下文捕获,则线程将继续直接执行该方法。如果不禁用上下文捕获,则线程将重新进入请求上下文,然后继续执行该方法。

所以ConfigureAwait(false)不会在ASP.NET中保存线程跳转;它确实可以帮助您重新输入请求上下文,但这通常非常快。如果您尝试对请求执行少量并行处理,ConfigureAwait(false) 可能非常有用,但实际上TPL更适合大多数情况。

  

但是,使用ASP.NET Web Api,如果您的请求是在一个线程上进行的,并且您等待某个函数并调用ConfigureAwait(false),这可能会在您返回最终结果时将您置于不同的线程上你的ApiController功能。

实际上,只做一个await就可以做到这一点。在async方法点击await后,方法会被阻止,但线程会返回到线程池。当方法准备好继续时,从线程池中抢夺任何线程并用于恢复该方法。

ASP.NET中唯一的区别是{1}}该线程在恢复方法时是否进入请求上下文。

我的MSDN article on SynchronizationContext和我的async intro blog post中有更多背景信息。

答案 1 :(得分:122)

您的问题的简要回答:不可以。您不应该在应用程序级别调用ConfigureAwait(false)

TL;漫长答案的DR版本:如果您正在编写一个您不了解您的消费者并且不需要同步上下文的库(我不相信您不应该在库中),那么您应该始终使用ConfigureAwait(false)。否则,您的库的使用者可能会以阻塞方式使用异步方法而面临死锁。这取决于具体情况。

以下是ConfigureAwait方法重要性的更详细说明(我的博客文章引用):

  

当您在等待关键字,编译器的方法上等待时   代表您生成一堆代码。其中一个目的   action是处理与UI(或主)线程的同步。钥匙   此功能的组件是SynchronizationContext.Current   获取当前线程的同步上下文。   SynchronizationContext.Current的填充取决于   你所处的环境。任务的GetAwaiter方法可以查找   SynchronizationContext.Current。如果是当前同步上下文   不为null,传递给该awaiter的延续将得到   回发到同步上下文。

     

使用新方法时使用新的异步语言   功能,以阻塞的方式,你最终会遇到死锁   你有一个可用的SynchronizationContext。当你消费时   阻塞方式的这些方法(等待任务等待   方法或直接从结果属性中获取结果   任务),您将同时阻止主线程。什么时候   最终,任务在线程池中的该方法内完成,它   将调用延续回发到主线程   因为SynchronizationContext.Current可用并已捕获。但   这里有一个问题:UI线程被阻止,你有一个   死锁!

此外,这里有两篇精彩的文章,完全适合您的问题:

最后,来自Lucian Wischik的精彩短片视频主题为Async library methods should consider using Task.ConfigureAwait(false)

希望这有帮助。

答案 2 :(得分:15)

我使用ConfigureAwait(false)找到的最大缺点是线程文化被恢复为系统默认值。如果你已经配置了一种文化,例如......

<system.web>
    <globalization culture="en-AU" uiCulture="en-AU" />    
    ...

并且您在其文化设置为en-US的服务器上托管,然后您将在ConfigureAwait(false)之前找到CultureInfo.CurrentCulture将返回en-AU并在您获得en-US之后。 即。

// CultureInfo.CurrentCulture ~ {en-AU}
await xxxx.ConfigureAwait(false);
// CultureInfo.CurrentCulture ~ {en-US}

如果您的应用程序正在执行任何需要特定于文化的数据格式化的操作,那么在使用ConfigureAwait(false)时您需要注意这一点。

答案 3 :(得分:8)

我对Task

的实施有一些一般性的想法
  1. 任务是一次性的,但我们not supposed to使用using
  2. ConfigureAwait在4.5中引入。 Task在4.0中引入。
  3. .NET Threads 总是用于流动上下文(请参阅C#通过CLR书),但在Task.ContinueWith的默认实现中,他们没有b / c它实现了上下文切换是昂贵的它默认关闭。
  4. 问题是库开发人员不应该关心客户端是否需要上下文流,因此它不应该决定是否流动上下文。
  5. [后来补充]事实上没有权威的答案和适当的参考,我们继续为此而战,这意味着某人没有正确地完成工作。
  6. 关于这个问题,我有一些posts,但我对Tugberk的不错回答是 - 你应该把所有API变为异步,理想情况下是上下文。既然你正在执行异步,您可以简单地使用continuation而不是等待,因此不会导致死锁,因为在库中没有等待并且您保持流动以便保留上下文(例如HttpContext)。

    问题是当库公开同步API但使用其他异步API时 - 因此您需要在代码中使用Wait() / Result