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

当你有服务器端代码(即一些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;
}
}
264907 次浏览

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

长答案的TL;DR版本:如果你正在编写一个库,你不知道你的消费者,也不需要同步上下文(我认为你不应该在一个库中),你应该总是使用ConfigureAwait(false)。否则,库的消费者可能会以阻塞的方式使用异步方法而面临死锁。这取决于具体情况。

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

当你正在等待一个方法,关键字await,编译器 为你生成一堆代码。这是目的之一 action是处理与UI(或主线程)的同步。的关键 这个特性的组件是SynchronizationContext.Current 获取当前线程的同步上下文。 类型填充SynchronizationContext.Current 你所在的环境。Task的GetAwaiter方法查找 # EYZ0。如果当前同步上下文为 不是null,传递给侍者的延续

当使用一个方法时,它使用新的异步语言 功能,在阻塞的方式下,你将以死锁结束,如果 您有一个可用的SynchronizationContext。当你消费的时候 这样的方法以阻塞的方式(等待任务与等待 方法的result属性直接获取结果 任务),您将同时阻塞主线程。当 最终Task在线程池中的该方法内部完成 是否会调用continuation来发布回主线程 因为SynchronizationContext.Current是可用的并且被捕获了。但 这里有一个问题:UI线程被阻塞,你有一个 僵局!< / p >

另外,这里有两篇很棒的文章,正是针对你的问题:

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

希望这能有所帮助。

我有一些关于Task实现的一般性想法:

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

我有一些关于这个主题的的帖子,但我的观点——除了Tugberk的漂亮答案——是你应该把所有的api都变成异步的,并且理想情况下是在上下文中流动。,因为你在做异步,你可以简单地使用延续而不是等待,这样就不会导致死锁,因为在库中没有等待,你保持流动,所以上下文被保留(比如HttpContext)。

# EYZ2

# EYZ2 # EYZ3。如果你是ASP。NET Core,不管你是否使用ConfigureAwait(false)

ASP。NET“完整”或“经典”或其他什么,这个答案的其余部分仍然适用。

原帖(非核心ASP.NET):

这个视频由ASP。NET团队有关于在ASP.NET上使用async的最佳信息

我曾经读到过,它的性能更高,因为它不需要将线程上下文切换回原始线程上下文。

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方法将被阻塞,而线程将返回线程池。当该方法准备继续时,将从线程池中获取任何线程并用于继续该方法。

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

我有更多的背景信息在我的MSDN文章SynchronizationContextasync介绍博客文章

我发现使用ConfigureAwait(false)最大的缺点是线程区域性会恢复到系统默认值。如果你已经配置了一个文化,例如…

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

并且您托管在一个区域性设置为en-US的服务器上,那么您将发现在ConfigureAwait(false)之前称为CultureInfo。CurrentCulture将返回en-AU,之后将返回en-US。 例如< / p >

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

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