这是一个WinForms代码:
async void Form1_Load(object sender, EventArgs e)
{
// on the UI thread
Debug.WriteLine(new { where = "before",
Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread });
var tcs = new TaskCompletionSource<bool>();
this.BeginInvoke(new MethodInvoker(() => tcs.SetResult(true)));
await tcs.Task.ContinueWith(t => {
// still on the UI thread
Debug.WriteLine(new { where = "ContinueWith",
Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread });
}, TaskContinuationOptions.ExecuteSynchronously).ConfigureAwait(false);
// on a pool thread
Debug.WriteLine(new { where = "after",
Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread });
}
输出:
{ where = before, ManagedThreadId = 10, IsThreadPoolThread = False } { where = ContinueWith, ManagedThreadId = 10, IsThreadPoolThread = False } { where = after, ManagedThreadId = 11, IsThreadPoolThread = True }
为什么ConfigureAwait主动将await
延续推送到池线程?
continueOnCapturedContext ... true试图编组 继续回到原始上下文;否则,错误。
我理解当前线程上安装了WinFormsSynchronizationContext
。仍然,没有尝试编组,执行点已经存在。
因此,它更像是&#34;永远不会继续捕获原始上下文&#34; ......
正如预期的那样,如果执行点已经在没有同步上下文的池线程上,则没有线程切换:
await Task.Delay(100).ContinueWith(t =>
{
// on a pool thread
Debug.WriteLine(new { where = "ContinueWith",
Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread });
}, TaskContinuationOptions.ExecuteSynchronously).ConfigureAwait(false);
{ where = before, ManagedThreadId = 10, IsThreadPoolThread = False } { where = ContinueWith, ManagedThreadId = 6, IsThreadPoolThread = True } { where = after, ManagedThreadId = 6, IsThreadPoolThread = True }
我即将查看implementation of ConfiguredTaskAwaitable
的答案。
已更新,还有一项测试,看看是否任何同步。上下文不足以继续(而不是原始的)。确实如此:
class DumbSyncContext: SynchronizationContext
{
}
// ...
Debug.WriteLine(new { where = "before",
Thread.CurrentThread.ManagedThreadId,
Thread.CurrentThread.IsThreadPoolThread });
var tcs = new TaskCompletionSource<bool>();
var thread = new Thread(() =>
{
Debug.WriteLine(new { where = "new Thread",
Thread.CurrentThread.ManagedThreadId,
Thread.CurrentThread.IsThreadPoolThread});
SynchronizationContext.SetSynchronizationContext(new DumbSyncContext());
tcs.SetResult(true);
Thread.Sleep(1000);
});
thread.Start();
await tcs.Task.ContinueWith(t => {
Debug.WriteLine(new { where = "ContinueWith",
Thread.CurrentThread.ManagedThreadId,
Thread.CurrentThread.IsThreadPoolThread});
}, TaskContinuationOptions.ExecuteSynchronously).ConfigureAwait(false);
Debug.WriteLine(new { where = "after",
Thread.CurrentThread.ManagedThreadId,
Thread.CurrentThread.IsThreadPoolThread });
{ where = before, ManagedThreadId = 9, IsThreadPoolThread = False } { where = new Thread, ManagedThreadId = 10, IsThreadPoolThread = False } { where = ContinueWith, ManagedThreadId = 10, IsThreadPoolThread = False } { where = after, ManagedThreadId = 6, IsThreadPoolThread = True }
答案 0 :(得分:13)
为什么ConfigureAwait主动将await continuation推送到池线程?
它不会“将其推送到线程池线程”,就像说“不要强迫自己回到之前的SynchronizationContext
”。
如果你没有捕获现有的上下文,那么在await
之后处理代码的延续只会在线程池线程上运行,因为没有要编组的上下文。
现在,这与“推送到线程池”略有不同,因为当你执行ConfigureAwait(false)
时,没有保证它将在线程池上运行。如果你打电话:
await FooAsync().ConfigureAwait(false);
FooAsync()
可能会同步执行,在这种情况下,您永远不会离开当前上下文。在这种情况下,ConfigureAwait(false)
没有实际效果,因为await
功能创建的状态机会短路并直接运行。
如果您希望看到这一点,请创建一个异步方法,如下所示:
static Task FooAsync(bool runSync)
{
if (!runSync)
await Task.Delay(100);
}
如果你这样称呼:
await FooAsync(true).ConfigureAwait(false);
您将看到您保留在主线程上(假设这是await之前的当前上下文),因为在代码路径中没有执行实际的异步代码。但是,FooAsync(false).ConfigureAwait(false);
的相同调用将导致它在执行后跳转到线程池线程。
答案 1 :(得分:10)
以下是基于挖掘.NET Reference Source的这种行为的解释。
如果使用ConfigureAwait(true)
,则继续通过使用SynchronizationContextTaskScheduler
的{{3}}完成,在这种情况下一切都很清楚。
如果使用 ConfigureAwait(false)
(或者如果没有要捕获的同步。上下文),则通过TaskSchedulerAwaitTaskContinuation
完成,它会尝试内联续展首先执行任务,如果无法进行内联,则使用ThreadPool
对其进行排队。
内联由AwaitTaskContinuation
确定,它永远不会在具有自定义同步上下文的线程上内联任务。但是它最好在当前池线程上内联它。这就解释了为什么我们在第一种情况下推送池线程,并在第二种情况下保持在同一个池线程中(使用Task.Delay(100)
)。
答案 2 :(得分:8)
我认为以最略微不同的方式考虑这一点是最容易的。
假设你有:
await task.ConfigureAwait(false);
首先,如果task
已经完成,那么正如Reed指出的那样,ConfigureAwait
实际上被忽略并且执行继续(同步,在同一个线程上)。
否则,await
将暂停该方法。在这种情况下,当await
恢复并看到ConfigureAwait
为false
时,有一个特殊的逻辑来检查代码是否有SynchronizationContext
并在线程池上恢复就是这样。这是无证件的,但不是不正当的行为。因为它没有证件,我建议你不要依赖行为;如果要在线程池上运行某些内容,请使用Task.Run
。 ConfigureAwait(false)
字面意思是“我不关心这种方法恢复的背景。”
请注意,ConfigureAwait(true)
(默认设置)会继续使用当前SynchronizationContext
或 TaskScheduler
上的方法。虽然ConfigureAwait(false)
将继续使用除之外的任何线程上的方法SynchronizationContext
。他们并不完全相反。