639

当您有服务器端代码(即一些ApiController)并且您的函数是异步的 - 所以它们返回时Task<SomeObject>- 任何时候等待调用的函数是否被认为是最佳实践ConfigureAwait(false)

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

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

public class CustomerController : ApiController
{
    public async Task<Customer> Get(int id)
    {
        // you are on a particular thread here
        var customer = await GetCustomerAsync(id).ConfigureAwait(false);
        
        // now you are on a different thread!  will that cause problems?
        return customer;
    }
}
4

4 回答 4

729

更新: ASP.NET Core 没有SynchronizationContext. 如果你在 ASP.NET Core 上,用不用都没关系ConfigureAwait(false)

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

原始帖子(针对非核心 ASP.NET):

由 ASP.NET 团队制作的此视频包含有关async在 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 中唯一的区别ConfigureAwait是该线程在恢复方法时是否进入请求上下文。

我在我的MSDN 文章SynchronizationContext和我的async介绍性博客文章中有更多背景信息。

于 2012-11-21T13:40:46.267 回答
145

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

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

下面是关于方法重要性的更详细的解释ConfigureAwait(引自我的博客文章):

当您使用 await 关键字等待方法时,编译器会代表您生成一堆代码。此操作的目的之一是处理与 UI(或主)线程的同步。此功能的关键组件是SynchronizationContext.Current获取当前线程的同步上下文。 SynchronizationContext.Current根据您所处的环境填充。GetAwaiterTask 的方法查找 SynchronizationContext.Current. 如果当前同步上下文不为空,则传递给该等待者的延续将被回发到该同步上下文。

当以阻塞方式使用使用新异步语言特性的方法时,如果您有可用的 SynchronizationContext,您将最终陷入死锁。当您以阻塞方式使用此类方法时(使用 Wait 方法等待 Task 或直接从 Task 的 Result 属性中获取结果),您将同时阻塞主线程。当最终任务在线程池中的该方法内完成时,它将调用延续以回发到主线程,因为SynchronizationContext.Current它可用并被捕获。但是这里有个问题:UI线程被阻塞了,你就死锁了!

此外,这里有两篇非常适合您的文章,它们正是针对您的问题:

最后,Lucian Wischik有一个关于这个主题的精彩短视频:异步库方法应该考虑使用 Task.ConfigureAwait(false)

希望这可以帮助。

于 2012-11-21T09:01:21.073 回答
33

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

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

并且您托管在其文化设置为 en-US 的服务器上,那么您会在 ConfigureAwait(false) 被调用之前发现 CultureInfo.CurrentCulture 将返回 en-AU 并且在您将获得 en-US 之后。IE

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

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

于 2017-06-22T02:49:01.287 回答
13

我有一些关于实施的一般想法Task

  1. 任务是一次性的,但我们不应该使用using.
  2. ConfigureAwait在 4.5 中引入。Task在 4.0 中引入。
  3. .NET 线程总是用于流动上下文(参见 C# via CLR book),但在它们的默认实现中,Task.ContinueWith它们并不 b/c,它意识到上下文切换是昂贵的,默认情况下它是关闭的。
  4. 问题是库开发人员不应该关心其客户是否需要上下文流,因此它不应该决定是否流上下文。
  5. [稍后添加] 没有权威的答案和适当的参考,我们一直在为此奋斗,这意味着有人没有做好他们的工作。

我有几篇关于这个主题的帖子,但我的看法——除了 Tugberk 的好答案——你应该把所有的 API 都变成异步的,并且最好让上下文流动。由于您正在执行异步操作,因此您可以简单地使用延续而不是等待,因此不会导致死锁,因为库中没有等待,并且您保持流动,因此保留了上下文(例如 HttpContext)。

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

于 2012-11-21T13:23:29.190 回答