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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/13489065/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 08:44:25  来源:igfitidea点击:

Best practice to call ConfigureAwait for all server-side code

c#asp.net-web-apitask-parallel-libraryasync-await

提问by Arash Emami

When you have server-side code (i.e. some ApiController) and your functions are asynchronous - so they return Task<SomeObject>- is it considered best practice that any time you await functions that you call ConfigureAwait(false)?

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

I had read that it is more performant since it doesn't have to switch thread contexts back to the original thread context. However, with ASP.NET Web Api, if your request is coming in on one thread, and you await some function and call ConfigureAwait(false)that could potentially put you on a different thread when you are returning the final result of your ApiControllerfunction.

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

I've typed up an example of what I am talking about below:

我已经输入了一个我在下面谈论的例子:

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;
    }
}

采纳答案by Stephen Cleary

Update:ASP.NET Core does not have a SynchronizationContext. If you are on ASP.NET Core, it does not matter whether you use ConfigureAwait(false)or not.

更新:ASP.NET Core 没有SynchronizationContext. 如果您使用的是 ASP.NET Core,则使用ConfigureAwait(false)与否都没有关系。

For ASP.NET "Full" or "Classic" or whatever, the rest of this answer still applies.

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

Original post (for non-Core ASP.NET):

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

This video by the ASP.NET team has the best information on using asyncon ASP.NET.

ASP.NET 团队的这段视频提供了有关async在 ASP.NET上使用的最佳信息。

I had read that it is more performant since it doesn't have to switch thread contexts back to the original thread context.

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

This is true with UI applications, where there is only one UI thread that you have to "sync" back to.

UI 应用程序也是如此,其中只有一个您必须“同步”回的 UI 线程。

In ASP.NET, the situation is a bit more complex. When an asyncmethod resumes execution, it grabs a thread from the ASP.NET thread pool. If you disable the context capture using ConfigureAwait(false), then the thread just continues executing the method directly. If you do not disable the context capture, then the thread will re-enter the request context and then continue to execute the method.

在 ASP.NET 中,情况有点复杂。当一个async方法恢复执行时,它从 ASP.NET 线程池中获取一个线程。如果您使用 禁用上下文捕获ConfigureAwait(false),则线程将继续直接执行该方法。如果不禁用上下文捕获,那么线程将重新进入请求上下文,然后继续执行该方法。

So ConfigureAwait(false)does not save you a thread jump in ASP.NET; it does save you the re-entering of the request context, but this is normally very fast. ConfigureAwait(false)couldbe useful if you're trying to do a small amount of parallel processing of a request, but really TPL is a better fit for most of those scenarios.

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

However, with ASP.NET Web Api, if your request is coming in on one thread, and you await some function and call ConfigureAwait(false) that could potentially put you on a different thread when you are returning the final result of your ApiController function.

但是,对于 ASP.NET Web Api,如果您的请求来自一个线程,并且您等待某个函数并调用 ConfigureAwait(false),当您返回 ApiController 函数的最终结果时,这可能会将您置于不同的线程.

Actually, just doing an awaitcan do that. Once your asyncmethod hits an await, the methodis blocked but the threadreturns to the thread pool. When the method is ready to continue, any thread is snatched from the thread pool and used to resume the method.

其实,只要做一个await就可以做到。一旦您的async方法命中await,该方法将被阻塞,但线程返回到线程池。当方法准备好继续时,任何线程都会从线程池中被抢走并用于恢复该方法。

The only difference ConfigureAwaitmakes in ASP.NET is whether that thread enters the request context when resuming the method.

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

I have more background information in my MSDN article on SynchronizationContextand my asyncintro blog post.

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

回答by tugberk

Brief answer to your question: No. You shouldn't call ConfigureAwait(false)at the application level like that.

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

TL;DR version of the long answer: If you are writing a library where you don't know your consumer and don't need a synchronization context (which you shouldn't in a library I believe), you should always use ConfigureAwait(false). Otherwise, the consumers of your library may face deadlocks by consuming your asynchronous methods in a blocking fashion. This depends on the situation.

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

Here is a bit more detailed explanation on the importance of ConfigureAwaitmethod (a quote from my blog post):

以下是对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.Currentwhich gets the synchronization context for the current thread. SynchronizationContext.Currentis populated depending on the environment you are in. The GetAwaitermethod 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.Currentis available and captured. But there is a problem here: the UI thread is blocked and you have a deadlock!

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

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

Also, here are two great articles for you which are exactly for your question:

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

Finally, there is a great short video from Lucian Wischikexactly on this topic: Async library methods should consider using Task.ConfigureAwait(false).

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

Hope this helps.

希望这可以帮助。

回答by Aliostad

I have some general thoughts about the implementation of Task:

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

  1. Task is disposable yet we are not supposed touse using.
  2. ConfigureAwaitwas introduced in 4.5. Taskwas introduced in 4.0.
  3. .NET Threads alwaysused to flow the context (see C# via CLR book) but in the default implementation of Task.ContinueWiththey do not b/c it was realised context switch is expensive and it is turned off by default.
  4. 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.
  5. [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.
  1. Task 是一次性的,但我们不应该使用using.
  2. ConfigureAwait在 4.5 中引入。Task是在 4.0 中引入的。
  3. .NET 线程总是用于流动上下文(参见 C# 通过 CLR 书),但在Task.ContinueWith它们的默认实现中不 b/c 意识到上下文切换是昂贵的,并且默认情况下是关闭的。
  4. 问题是库开发人员不应该关心它的客户是否需要上下文流,因此它不应该决定是否流过上下文。
  5. [稍后补充] 没有权威的答案和适当的参考,我们一直在争论这个事实意味着有人没有正确地完成他们的工作。

I have got a few postson the subject but my take - in addition to Tugberk's nice answer - is that you should turn all APIs asynchronous and ideally flow the context .Since you are doing async, you can simply use continuations instead of waiting so no deadlock will be cause since no wait is done in the library and you keep the flowing so the context is preserved (such as HttpContext).

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

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

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

回答by Mick

The biggest draw back I've found with using ConfigureAwait(false) is that the thread culture is reverted to the system default. If you've configured a culture e.g ...

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

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

and you're hosting on a server whose culture is set to en-US, then you will find before ConfigureAwait(false) is called CultureInfo.CurrentCulture will return en-AU and after you will get en-US. i.e.

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

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

If your application is doing anything which requires culture specific formatting of data, then you'll need to be mindful of this when using ConfigureAwait(false).

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