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

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

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

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

When you are awaiting on a method with await keyword, compiler generates bunch of code in behalf of you. One of the purposes of this action is to handle synchronization with the UI (or main) thread. The key component of this feature is the SynchronizationContext.Current which gets the synchronization context for the current thread. SynchronizationContext.Current is populated depending on the environment you are in. The GetAwaiter method of Task looks up for SynchronizationContext.Current. If current synchronization context is not null, the continuation that gets passed to that awaiter will get posted back to that synchronization context. When consuming a method, which uses the new asynchronous language features, in a blocking fashion, you will end up with a deadlock if you have an available SynchronizationContext. When you are consuming such methods in a blocking fashion (waiting on the Task with Wait method or taking the result directly from the Result property of the Task), you will block the main thread at the same time. When eventually the Task completes inside that method in the threadpool, it is going to invoke the continuation to post back to the main thread because SynchronizationContext.Current is available and captured. But there is a problem here: the UI thread is blocked and you have a deadlock!

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

搬起石头砸自己的脚——使用c# 5.0异步语言特性导致僵局 为您的HTTP API提供异步.NET客户端库,并意识到async/await的不良影响

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


对于Task的执行,我有一些大致的想法:

Task is disposable yet we are not supposed to use using. ConfigureAwait was introduced in 4.5. Task was introduced in 4.0. .NET Threads always used to flow the context (see C# via CLR book) but in the default implementation of Task.ContinueWith they do not b/c it was realised context switch is expensive and it is turned off by default. The problem is a library developer should not care whether its clients need context flow or not hence it should not decide whether flow the context or not. [Added later] The fact that there is no authoritative answer and proper reference and we keep fighting on this means someone has not done their job right.

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

问题是当一个库公开了一个同步API,但使用了另一个异步API时——因此你需要在代码中使用Wait()/Result。


更新:ASP。NET Core没有SynchronizationContext。如果你是ASP。NET Core,不管你是否使用ConfigureAwait(false)。

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

原帖(非核心ASP.NET):

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

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

UI应用程序就是这样,只有一个UI线程需要“同步”回去。

在ASP。NET,情况有点复杂。当异步方法恢复执行时,它会从ASP。NET线程池。如果您使用ConfigureAwait(false)禁用上下文捕获,那么线程只是直接继续执行该方法。如果您没有禁用上下文捕获,那么线程将重新进入请求上下文,然后继续执行该方法。

所以ConfigureAwait(false)不会在ASP.NET中为您节省线程跳转;它确实节省了重新输入请求上下文的时间,但这通常非常快。如果您试图对请求进行少量的并行处理,那么ConfigureAwait(false)可能很有用,但实际上TPL更适合大多数这些场景。

然而,使用ASP。NET Web Api,如果你的请求来自一个线程,你在等待某个函数并调用ConfigureAwait(false),当你返回ApiController函数的最终结果时,这可能会让你在另一个线程上。

实际上,只要执行一个await就可以做到。一旦async方法命中await,该方法将被阻塞,但线程将返回到线程池。当该方法准备继续时,将从线程池中获取任何线程并用于继续该方法。

ConfigureAwait在ASP。NET是线程在恢复方法时是否进入请求上下文。

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


我发现使用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)时需要注意这一点。