当您有服务器端代码(即某些ApiController)并且您的函数是异步的(因此它们返回Task)时,您是否认为最好的做法是随时等待您调用ConfigureAwait(false)的函数?
我曾经读到它更具性能,因为它不必将线程上下文切换回原始线程上下文。但是,使用ASP.NET Web API,如果您的请求来自一个线程,并且您等待某个函数并调用ConfigureAwait(false),当您返回ApiController函数的最终结果时,可能会将您置于另一个线程上。
我在下面打了一个例子:
1 2 3 4 5 6 7 8 9 10 11
| 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;
}
} |
更新:ASP.NET核心没有SynchronizationContext。如果您使用的是ASP.NET核心,那么您是否使用ConfigureAwait(false)并不重要。
对于ASP.NET"full"或"classic"或其他内容,此答案的其余部分仍然适用。
原始日志(对于非核心ASP.NET):
ASP.NET团队的这段视频提供了有关在ASP.NET上使用async的最佳信息。
I had read that it is more performant since it doesn't have to switch thread contexts back to the original thread context.
这对于UI应用程序是正确的,因为只有一个UI线程需要"同步"回去。
在ASP.NET中,情况有点复杂。当async方法恢复执行时,它从ASP.NET线程池中获取线程。如果使用ConfigureAwait(false)禁用上下文捕获,那么线程将继续直接执行该方法。如果不禁用上下文捕获,那么线程将重新进入请求上下文,然后继续执行该方法。
因此,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.
实际上,仅仅做一个await就可以做到。当您的async方法命中await时,该方法被阻塞,但线程返回线程池。当方法准备好继续时,将从线程池中抓取任何线程并用于恢复该方法。
在ASP.NET中,ConfigureAwait唯一的区别是该线程在恢复方法时是否进入请求上下文。
在我关于SynchronizationContext和async简介博客文章的msdn文章中,我有更多的背景信息。
- 我的答案被删除了,所以不能在那里回答你。但我不混淆这里的上下文,我不了解你。上下文的含义是线程存储区域数据。默认情况下,上下文不会在ContinueWith中流动。TSA数据不会被复制-如果您认为没有,请证明我是错的,您可以通过查看HttpContext.Current来检查这一点。这就是为什么我们要穿过铁圈和铁圈来传递它。
- 线程本地存储不受任何上下文的影响。HttpContext.Current由ASP.NET SynchronizationContext流,在await时默认流,但不由ContinueWith流。Otoh,执行上下文(包括安全限制)是clr中通过c_提到的上下文,它由ContinueWith和await流(即使使用ConfigureAwait(false))。
- 谢谢你,斯蒂芬,我把你的帖子标为答案。我必须读几遍才能得到它,但似乎只有在桌面/移动应用程序中调用configureAwait(false)才有用,在该应用程序中,您可以进行异步调用(如httpwebrequest),而宁愿在UI线程外处理结果。否则,不值得为了使用ASP.NET时获得的任何小的性能提升而把代码搞得一团糟。
- 如果C语言支持configureAwait(false),这不是很好吗?类似于"等待"(不等待上下文)。在任何地方输入一个单独的方法调用都是非常烦人的。:)
- @纳撒纳登斯:讨论得相当多。新关键字的问题是,ConfigureAwait实际上只在等待任务时才有意义,而await对任何"等待"都有作用。考虑的其他选项有:如果在库中,默认行为是否应该丢弃上下文?或者有默认上下文行为的编译器设置?这两种方法都被拒绝了,因为仅仅阅读代码并说出它的作用是很困难的。
- @stephencleary我不理解您的行:如果使用configureawait(false)禁用上下文捕获,那么线程将继续直接执行该方法。-您是说线程没有回到线程池中,但仍在等待操作完成,当它完成后,它会继续回调吗?(使用同一线程)……-或者-您是在谈论异步操作完成时,线程池中的另一个/同一个线程将返回以继续回调,但它不会进入执行上下文….
- @royinamir:异步操作完成后,线程池中的线程将用于实际完成任务。如果使用ConfigureAwait(false),那么同一线程将继续执行async方法,而不输入请求上下文。(顺便说一句,这是一个实现细节;此行为是未记录的)。
- @StephenCleary,这是否意味着在WebAPI控制器中不应该使用configureAwait(false)?
- @Ansulnigam:当您不需要请求上下文时,应该使用ConfigureAwait(false)。
- @stephencleary,但不太可能不使用请求上下文,因为对于每个请求,都需要发送响应,类似于this.request.createResponse(httpstatuscode.xxx)
- @Ansulnigam:这就是为什么控制器操作需要它们的上下文。但是操作调用的大多数方法都没有。
- @史蒂芬克利里,难道不值得提及你在僵局上的其他观点吗?stackoverflow.com/questions/13140523/&hellip;
- @Jonathanroeder:一般来说,你不应该需要ConfigureAwait(false)来避免基于Result/Wait的死锁,因为在ASP.NET上,你不应该首先使用Result/Wait。
- @stephencleary:我是否从你的博客A good rule of thumb is to use ConfigureAwait(false) unless you know you do need the context中正确地理解了使用configureawait(false)会带来性能优势,因为当前上下文(例如asp.net上下文)没有流动,但是离开configureawait(false)应该不会造成功能影响?
- @埃里基:在某些情况下,它能给你更好的表现。它永远不会给你带来更差的表现。除非有人使用ConfigureAwait(false)作为异步同步攻击的一部分,否则不会对功能产生影响。
- @我不想在评论中问这个问题:stackoverflow.com/questions/33711136/&hellip;
- @用户4205580:你已经有两个很好的答案了。它没有像您期望的那样运行的原因是因为您的内部"异步"方法实际上是同步的(编译器明确地警告您这一点)。使其真正异步(例如,添加一个await Task.Delay(200);),您将看到线程返回到线程池,200毫秒后,从线程池中取出一个新线程来恢复该方法。
- @Stephencleary,正如我所看到的,根本原因是不正确地使用Result/Wait,其中ConfigureAwait(false)起到了解决问题的作用。所以为什么这么多人建议把这项工作放在任何地方而不仅仅是解决问题。例如,使用Task.Run(async () => { await ...}).Wait();就可以做到这一点,这比在库代码中到处放置100倍ConfigureAwait(false)要容易得多。为什么没有人建议?
- 以下是resharper plugins.jetbrains.com/packages/configureawitcheck&zwnj;&8203;er示例
- @stephencleary对您的评论感到困惑:"当您不需要请求上下文时,应该使用configureawait(false)",但在前面您说"httpcontext.current由ASP.NET同步上下文流,在您等待时默认流"。因此,任何方法都可以访问请求上下文,而不管如何?当我测试它时,无论是否调用ConfigureAwait(false),我都可以在我的方法中访问HttpContext.Request。
- "默认"是指"除非您使用ConfigureAwait(false)"。HttpContext.Request不经过HttpContext.Current;HttpContext.Current是一种静态属性。另外,请注意,多线程使用HttpContext并不安全;如果使用ConfigureAwait(false),则可以访问HttpContext.Request,但绝对不应该访问。
- @史蒂芬克利里,哦,误解了你的评论,我的错。谢谢你把它清理干净
- @Stephencleary对Neleus建议的替代解决方案有什么想法吗?
- @eglasius:只有当Task.Run执行的代码不依赖于当前上下文(例如,HttpContext.Current)、任何ASP.NET API(其中一些隐式依赖于ASP.NET上下文、应用于请求的依赖注入解析等)时,它才起作用。请记住,在这种情况下"有效"意味着"浪费线程"。await仍然是最好的解决方案。
- 是的,但是如果您正在运行任何需要上下文的异步代码,因此没有configureAwait(false),那么无论如何,您都会遇到死锁。这两种方法在这种情况下都不起作用,还是我遗漏了什么?
- @避免死锁的最好方法是完全不要阻塞异步代码。
- 我不能再同意了。在重新阅读了整个线索之后,我发现我没有很好地设置场景。我现在了解到的是:把它放在任何地方的原因是为了避免不必要地恢复上下文,这可以在性能上给您带来一些(小的)收益(对于死锁来说不是这样)。有些人在从非异步代码调用它时将其用于死锁(通常在大型代码库升级中,在这种情况下不可能一次性完全转移到异步)。在后面的这个例子中,我将各地的configureAwait(false)与单个任务进行了比较。运行,因为它很容易被遗漏并最终出错。
- 我的ASP.NET Web窗体应用程序也有同样的问题。我的问题说明了问题,但我找不到任何解决方案。我在每一次等待中都使用了ConfigureAwait(false),它工作了,但是当应用程序第一次运行时,页面按预期加载,但是如果我们再次请求页面,页面就不会再加载。@史蒂芬克利里能在这件事上提供帮助将是卓有成效的。谢谢
- @穆罕默德:我不知道WebMethod是否支持async。我从未使用过ASMX。(注:WebMethod为asmx,非webforms)。
- 我在代码隐藏文件中定义了WebMethod,每个aspx页面在其代码隐藏文件中都有大量WebMethod。[WebMethod] public static async Task
> GetXXX() => await new BusinessLogic().GetXXX().ConfigureAwait(false);
@stephencleary非常感谢您的意见,但由于我在一个场景中,您能给我任何建议吗?
- @Muhammadiqbal:我的建议是从ASMX转到WebAPI。
- @stephencleary请更新这篇文章,包括你最近的博客内容。stephencleary.com/2017/03/&hellip;
- 这个答案缺少的一点是全球化和文化。使用configureawait(false)会丢失web.config system.web/globalization设置。请参阅下面的答案了解更多详细信息
- @史蒂芬·克利里,你觉得Xamarin怎么样?在这种情况下,它和ASP.NET Core是一样的?
- @mehdiehghani:不。包括xamarin在内的UI框架具有同步上下文,并且代码必须在该上下文中才能访问UI元素。
简单回答你的问题:不,你不应该在这样的应用程序级别调用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客户端库以及异步/等待的不良影响的意识
最后,有一个来自LucianWischik的关于这个主题的很好的视频:异步库方法应该考虑使用task.configureawait(false)。
希望这有帮助。
- "任务的getawaiter方法查找synchronizationContext.current。如果当前同步上下文不为空,则传递给该等待者的继续将被发送回该同步上下文。"—我觉得您试图说Task遍历堆栈以获取SynchronizationContext,这是错误的。在调用Task之前抓取SynchronizationContext,如果SynchronizationContext.Current不为空,则其余代码在SynchronizationContext上继续。
- @卡斯佩罗,我也打算这么说。
- 调用者是否应该负责确保SynchronizationContext.Current是明确的/或在Task.Run()内调用库,而不是必须在整个类库中编写.ConfigureAwait(false)?
- @宾基:另一方面:(1)假设一个库被用于许多应用程序中,因此在库中一次性努力使其更容易应用是经济有效的;(2)假设库作者知道他编写的代码没有理由要求继续使用原始上下文,这是他由那些.ConfigureAwait(false)s.pe表示的。rhaps如果这是默认行为,那么对于库作者来说会更容易一些,但是我假设正确地编写库会比正确地编写应用程序更难一些。
- 另一种说法是,ConfigureAwait(false)不用于CustomerController.Get()中(或高级应用程序代码,包括UI事件处理程序),如果它以synchronizationContext开始,那么在方法的后面几乎肯定需要该上下文。它是用于实现不需要应用程序上下文的库代码,如SomeAsyncFunctionThatGetsCustomer()。
- 是否可以简单地在顶层使用configureAwait,或者它是否真的需要在堆栈的每一个调用上都存在?忘记在一个调用上执行它,然后使主线程死锁,这将很糟糕。
- @Michaelparker你在图书馆的每一个电话都需要它。
- 图书馆的作者为什么要溺爱消费者?如果消费者想要死锁,我为什么要阻止他们?
- @唐纳德,我想知道同样的问题,我完全同意你的看法。你发现什么最适合你?我目前认为最好在WebApiController级别配置EAwait(false)。
- @masterwok我开始四处寻找这个问题的一个好答案,但从我所能看到的所有答案都是关于如何在WPF应用程序中进行HTTP调用的某人意见的复制粘贴。最好的答案是,当您无理由不进行货物切割时,使用configureawait(false)。
- @菲利普科达斯,我想你从来没有遇到过这种僵局吧?如果你读了所有的答案,你会发现背后有一个原因,人们通常不会写额外的代码,因为这很有趣。
- @图伯克不,任何人都不应该。您不应该试图在同步函数中进行异步工作,这是不难避免的,也不是不可避免的。没有充分的理由做某事是愚蠢的。
- @菲利普考达斯,我认为你读的东西不对。在2012年提出这个问题时,有一些合法的案例需要以同步方式运行异步代码(例如,由于ASP.NET MVC对子操作的限制等)。比如这个:github.com/tugberkugurlu/bloggy/blob/&hellip;
- 如你所见,我不得不使用AsyncHelper.RunSync,因为ravendb客户端在库级别处理不正确,导致了这个pr:github.com/ravendb/ravendb/pull/545
- @因此,我的建议是,下次在做出进一步的判断之前,要先了解上下文。
- @Tugberk Well AsyncController从2013年起就出现在那里,所以不需要"不得不"了,这只是简单而已。我的问题是"在大多数情况下,您不应该这样做时,您应该始终使用configureawait(false)"。如果不需要SynchronizationContext,则应该使用它,而不是始终使用。如果您正在编写一个库,那么您应该编写一些测试用例,以确保您的库能够正常工作,并且性能良好,而不仅仅是因为有人发布了一篇博客文章而调用方法。
- 另外,如果您希望人们以同步的方式使用您的库,请提供一个同步API,这不会使人们更容易执行错误的实践。
- @FilipCordas您仍然不知道情况,当时的子操作不支持异步。不管你是否有AsyncController。
我使用configureAwait(false)找到的最大一个回调是线程区域性恢复为系统默认值。如果您配置了一个区域性,例如…
1 2 3
| <system.web>
<globalization culture="en-AU" uiCulture="en-AU" />
... |
并且您在一个区域性设置为en-us的服务器上进行托管,然后您将发现在configureAwait(false)被称为cultureInfo之前,currentCurance将返回en-au,然后您将获得en-us。即
1 2 3
| // CultureInfo.CurrentCulture ~ {en-AU}
await xxxx.ConfigureAwait(false);
// CultureInfo.CurrentCulture ~ {en-US} |
如果您的应用程序正在执行任何需要特定于区域性的数据格式的操作,那么在使用configureawait(false)时,您需要注意这一点。
- .NET的现代版本(我认为从4.6开始?)将跨线程传播文化,即使使用ConfigureAwait(false)。
- 谢谢你的信息。我们确实在使用.NET 4.5.2
我对Task的实施有一些总体看法:
任务是一次性的,但我们不应该使用using。
4.5中引入了ConfigureAwait。Task在4.0中引入。
.NET线程始终用于流式处理上下文(请参见C_via clr book),但在默认的Task.ContinueWith实现中,它们不是B/C,而是意识到上下文切换很昂贵,默认情况下它是关闭的。
问题在于,库开发人员不应该关心其客户机是否需要上下文流,因此不应该决定是否要流上下文。
[稍后补充]事实上,没有权威的答案和适当的参考,我们一直在努力,这意味着有人没有做好他们的工作。
我有一些关于这个主题的文章,但是我的看法——除了TugBerk的好答案之外——是您应该将所有API异步化,并理想地流动上下文。因为您正在进行异步操作,所以您可以简单地使用延续而不是等待,这样就不会导致死锁,因为库中没有进行等待,并且您保持流动,这样就可以保留上下文(如httpcontext)。
问题在于,当库公开同步API但使用另一个异步API时,因此您需要在代码中使用Wait()/Result。
- 1)如果你愿意,你可以打电话给Task.Dispose;你只是不需要绝大多数时间。2)在.NET 4.0中引入Task作为TPL的一部分,不需要ConfigureAwait;当添加async时,它们重新使用现有的Task类型,而不是发明新的Future。
- 3)您混淆了两种不同类型的"上下文"。c via clr中提到的"上下文"总是流动的,即使在Tasks中也是如此;ContinueWith控制的"上下文"是SynchronizationContext或TaskScheduler。这些不同的上下文在StephenToub的博客中有详细的解释。
- 4)库作者不需要关心其调用方是否需要上下文流,因为每个异步方法都是独立恢复的。因此,如果调用者需要上下文流,那么他们可以流它,不管库作者是否流它。
- 起初,你似乎在抱怨而不是回答问题。然后你说的是"上下文",除了.NET中有几种上下文,而且还不清楚是哪一种(或哪一种?)你在说什么?即使你自己没有感到困惑(但我认为你是,我相信过去没有上下文可以与Thread一起流动,但现在不再与ContinueWith()一起流动了),这也会让你的答案读起来很困惑。
- @是的,lib-dev不需要知道,这取决于客户端。我以为我说得很清楚,但我的措词不清楚。
- 第2项:不同意。async与此无关,你可以决定是否在4.0中流动。
- @Stephencleary谢谢你的链接。也许我把他们弄糊涂了。)
- 还有一个一般性的想法:configureAwait不属于它现在的位置。假设您有一个异步方法x,它使用await和configureawait(y)调用100多个异步方法。这很愚蠢。y对于方法x很常见,这个配置等待"thing"属于x上的一个方法属性。
- 他们应该发明了未来,并按照现有的规格去做。JavaScript中的承诺更容易处理。作为一个库开发人员,如果我不在代码中添加configureawait(false),那么我所有的异步调用都是潜在的死锁。所有这些都可以让使用await和async关键字的小示例看起来不错。