ConfigureAwait(false) 不会使延续代码在另一个线程中运行
Posted
技术标签:
【中文标题】ConfigureAwait(false) 不会使延续代码在另一个线程中运行【英文标题】:ConfigureAwait(false) doesn't make the continuation code run in another thread 【发布时间】:2020-09-09 07:35:33 【问题描述】:我有一个在 thread1 上运行的代码。 我以同步方式调用函数(使用异步方法,但它不应该打扰我 - 异步方法不会将代码变为异步)。
我有一个将 ConfigureAwait 设置为 false 的等待,所以我理解 之后的代码是任务延续,假设在 不同 线程中运行,而不是在等待之前的代码 (因为 ConfigureAwait 设置为 false)。
根据我的测试 - 所有代码都在同一个线程中运行。 如何?为什么等待下面的代码不在不同的线程上运行? 这是代码:
public async void F1()
Console.WriteLine($"Thread.CurrentThread.ManagedThreadId=Thread.CurrentThread.ManagedThreadId");
int x = await F2().ConfigureAwait(false);
Console.WriteLine($"Thread.CurrentThread.ManagedThreadId=Thread.CurrentThread.ManagedThreadId");
private async Task<int> F2()
Console.WriteLine("Begins F2");
Console.WriteLine($"Thread.CurrentThread.ManagedThreadId=Thread.CurrentThread.ManagedThreadId");
Console.WriteLine("Finishes F2");
return 7;
这是输出:
Thread.CurrentThread.ManagedThreadId=1
Begins F2
Thread.CurrentThread.ManagedThreadId=1
Finishes F2
Thread.CurrentThread.ManagedThreadId=1
【问题讨论】:
您的代码实际上不是异步的。尝试至少将await Task.Delay(...)
或Task.Yield
添加到F2
抛开问题不谈,async void
是一种反模式,在大多数情况下不应使用:markheath.net/post/async-antipatterns
【参考方案1】:
这不是“应该在不同的线程上下文中运行”,而是允许。在相反的情况下 (ConfigureAwait(true)
),它必须在同一个 thread 上下文中继续。
此外,当没有要等待的东西时(在方法内部),“async”方法同步运行,因此不需要返回到一些线程上下文,它还在继续。
【讨论】:
你的意思是“它不应该在不同的线程中运行,它是允许的”??那我怎样才能强制它在不同的线程上运行呢? 它不必在同一个线程上继续,它必须在同一个上下文中继续。例如。经典的 ASP.Net 同步上下文并不能保证您将获得哪个线程,只是它具有对请求等内容的独占访问权限。【参考方案2】:我有一个将 ConfigureAwait 设置为 false 的等待,因此我理解它是一个任务延续之后的代码,它假设在与等待之前的代码不同的线程中运行(因为 ConfigureAwait 设置为 false)。
没有。这里有几个误解。
第一个误解是关于ConfigureAwait(false)
的作用。
ConfigureAwait
(和await
)与线程直接无关。 By default, await
captures a context - 当前的 SynchronizationContext
或 TaskScheduler
。这个上下文可以在同一个线程上恢复(例如,UI SynchronizationContext
s 通常这样做),但“上下文”并不一定意味着“线程”(例如,ASP.NET pre-Core @ 987654335@ 可以在任何线程池线程上恢复。
ConfigureAwait(false)
实际上所做的是跳过捕获该上下文。所以使用了线程池上下文,它可以运行在任何线程池线程上。请注意,如果 await
之前的代码在线程池线程上运行,它可能会在 任何 线程池线程上恢复,包括之前运行的线程。
第二个误解是关于ConfigureAwait(false)
何时被应用。
await
will first check to see if its awaitable is complete,并且只有这样它才会真正异步运行。所以如果你await
一个已经完成的任务,ConfigureAwait(false)
甚至不会被考虑 - 代码只是继续同步运行。
那我怎样才能强制它在不同的线程上运行呢?
使用Task.Run
。当您需要在线程池线程上运行代码时,Task.Run
是合适的工具。
【讨论】:
好吧,我绝对不知道线程和上下文之间的区别。我将阅读您在docs.microsoft.com/he-il/archive/msdn-magazine/2011/february/… 中写的这篇文章,以了解这个主题。你会说我需要为这个目标阅读更好的东西吗? @SItzik:如果您想深入了解SynchonizationContext
,这是一篇很好的文章,这是await
可以使用的两种上下文类型之一。如果您想了解有关此答案所讨论内容的更多信息,我推荐the blog post I linked to 并跟进Don't Block on Async Code 和async best practices。
我已经阅读了您链接到的这些文章(全部 3 篇)。谢谢你。仍然有一个 q 保持打开状态。如果上下文是 UI,那么 configureAwait(false) 会将上下文更改为任务调度程序,如果上下文是 Asp.Net - 那么同样,configureAwait(false) 会将上下文更改为任务调度程序。现在,如果上下文在任务调度程序的开头,那么 configureAwait(flase) 会将上下文更改为什么?答案不能是“另一个线程池”,因为另一个线程池意味着相同的上下文 - 任务调度程序。
@SItzik: ConfigureAwait(false)
不会改变上下文;它完全避免了它并使用线程池上下文。所以它会跳过 both SynchronizationContext
和 TaskScheduler
。
有趣,我将在您的许可下引用您:“上下文是当前的 SynchronizationContext,如果当前的 SynchronizationContext 为空,则为当前的 TaskScheduler。”这是来自blog.stephencleary.com/2012/07/dont-block-on-async-code.html。这句话意味着上下文只有 2 个选项: a) 当前 SynchronizationContext。 b) 任务调度器。没有“线程池”上下文。 UI线程上下文和ASP.NET都是SynchronizationContext的类型,所以剩下的唯一选择就是你提到的线程池上下文实际上是一个TaskScheduler。以上是关于ConfigureAwait(false) 不会使延续代码在另一个线程中运行的主要内容,如果未能解决你的问题,请参考以下文章
为整个项目/dll 设置 ConfigureAwait(false)
ConfigureAwait(false) 与将同步上下文设置为 null
您可以在没有线程安全的情况下使用 ConfigureAwait(false) 吗?