当你有服务器端代码(即某些 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 回答
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更适合大多数情况 .实际上,只是做一个
await
可以做到这一点 . 一旦async
方法命中await
,该方法就会被阻止,但线程会返回到线程池 . 当方法准备好继续时,从线程池中抢夺任何线程并用于恢复该方法 .ASP.NET中唯一的区别是,该线程在恢复方法时是否进入请求上下文 .
我在MSDN article on SynchronizationContext和我的async intro blog post中有更多背景信息 .
您的问题的简要回答:不 . 您不应该在应用程序级别上调用
ConfigureAwait(false)
.TL;答案的DR版本:如果你正在编写一个你不需要同步上下文的库(你不应该在我认为的库中),你应该总是使用
ConfigureAwait(false)
. 否则,库的使用者可能会以阻塞方式使用异步方法而面临死锁 . 这取决于具体情况 .这里有一个关于
ConfigureAwait
方法重要性的更详细解释(我的博客文章引用):另外,这里有两篇很棒的文章,完全适合您的问题:
The Perfect Recipe to Shoot Yourself in The Foot - Ending up with a Deadlock Using the C# 5.0 Asynchronous Language Features
Asynchronous .NET Client Libraries for Your HTTP API and Awareness of async/await's Bad Effects
最后,关于这个话题,Lucian Wischik有一个很棒的短视频:Async library methods should consider using Task.ConfigureAwait(false) .
希望这可以帮助 .
我在使用ConfigureAwait(false)时发现的最大缺点是线程文化被恢复为系统默认值 . 如果你已经配置了一种文化,例如......
然后,您将托管在其文化设置为en-US的服务器上您将在ConfigureAwait(false)之前找到CultureInfo.CurrentCulture将返回en-AU并在您获得en-US之后 . 即
如果您的应用程序正在执行任何需要特定于文化的数据格式化的操作,那么在使用ConfigureAwait(false)时您需要注意这一点 .
我对
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.