首页 文章

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

提问于
浏览
435

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

4 回答

  • 472

    Update: ASP.NET Core does not have a SynchronizationContext . 如果您使用的是ASP.NET Core,那么使用 ConfigureAwait(false) 无关紧要 .

    对于ASP.NET“Full”或“Classic”等等,本答案的其余部分仍然适用 .

    Original post (for non-Core 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中唯一的区别是,该线程在恢复方法时是否进入请求上下文 .

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

  • 6

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

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

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

    当您在等待具有await关键字的方法时,编译器会代表您生成大量代码 . 此操作的目的之一是处理与UI(或主)线程的同步 . 此功能的关键组件是SynchronizationContext.Current,它获取当前线程的同步上下文 . 根据您所处的环境填充SynchronizationContext.Current.Task的GetAwaiter方法查找SynchronizationContext.Current . 如果当前同步上下文不为null,则传递给该awaiter的continuation将返回到该同步上下文 . 当使用阻塞方式使用新异步语言功能的方法时,如果有可用的SynchronizationContext,最终会出现死锁 . 当您以阻塞方式使用此类方法(等待Task with Wait方法或直接从Task的Result属性获取结果)时,您将同时阻止主线程 . 当最终任务在线程池中的该方法内完成时,它将调用延续以回发到主线程,因为SynchronizationContext.Current可用并被捕获 . 但这里有一个问题:UI线程被阻止,你有一个死锁!

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

    最后,关于这个话题,Lucian Wischik有一个很棒的短视频:Async library methods should consider using Task.ConfigureAwait(false) .

    希望这可以帮助 .

  • 9

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

  • 115

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

    • 任务是一次性的,但我们not supposed to使用 using .

    • ConfigureAwait 在4.5中引入 . Task 在4.0中引入 .

    • .NET线程总是用于流动上下文(请参阅C#通过CLR书)但是在 Task.ContinueWith 的默认实现中,它们不是b / c它实现了上下文切换是昂贵的并且它默认关闭 .

    • 问题是库开发人员不应该关心它的客户端是否需要上下文流,因此它不应该决定是否流动上下文 .

    • [后来补充]事实上没有权威的答案和适当的参考,我们继续为此而战,这意味着有人没有做好自己的工作 .

    我有一些posts关于这个问题,但我的接受除了Tugberk的好答案 - 是 you should turn all APIs asynchronous and ideally flow the context . 因为你正在做异步,你可以简单地使用延续而不是等待因此不会导致死锁因为没有等待库和你保持流动,以保持上下文(如HttpContext) .

    Problem is when a library exposes a synchronous API but uses another asynchronous API - hence you need to use Wait()/Result in your code.

相关问题