c# - configureawait用法




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

更新: ASP.NET Core没有SynchronizationContext 。 如果您使用的是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.NET中唯一的区别是该线程在恢复方法时是否进入请求上下文。

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

当你有服务器端代码(例如一些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 SomeAsyncFunctionThatGetsCustomer(id).ConfigureAwait(false);

        // now you are on a different thread!  will that cause problems?
        return customer;
    }
}

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


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

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

这里有一些关于ConfigureAwait方法的重要性的更详细的解释(来自我博客文章的引用):

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

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

另外,这里有两篇很棒的文章,适合你的问题:

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

希望这可以帮助。







async-await