ConfigureAwait将继续推送到池线程

这是一个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延续推到池线程?

MSDN文档说:

continueOnCapturedContext … true尝试将继续编组回到捕获的原始上下文; 否则,是错误的。

我明白在当前线程上安装了WinFormsSynchronizationContext 。 尽pipe如此,还没有企图执行,执行点已经在那里。

因此,它更像是“永远不会继续捕捉原始的上下文”

正如所料,如果执行点已经在没有同步上下文的池线程上,那么就不会有线程切换:

 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}

我即将查看ConfiguredTaskAwaitable实现的答案。

更新一个testing,看看是否有任何同步。 上下文对于继续而言不够好(而不是原来的)。 事实确实如此:

 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}

为什么ConfigureAwait主动将await延续推到池线程?

它不会像“不要强迫自己回到先前的SynchronizationContext ”一样“推送到线程池线程”。

如果不捕获现有的上下文,那么处理代码之后的继续将仅仅在线程池线程上运行,因为没有上下文来编组。

现在,这与“推入线程池”有细微的差别,因为当您执行ConfigureAwait(false)时,不能保证它将在线程池上运行。 如果你打电话给:

 await FooAsync().ConfigureAwait(false); 

FooAsync()可能会同步执行,在这种情况下,您将永远不会离开当前的上下文。 在这种情况下, ConfigureAwait(false)没有实际效果,因为由await特性创build的状态机将会短路并直接运行。

如果你想看到这个在行动,做一个asynchronous的方法是这样的:

 static Task FooAsync(bool runSync) { if (!runSync) await Task.Delay(100); } 

如果你这样称呼:

 await FooAsync(true).ConfigureAwait(false); 

你会看到你停留在主线程上(因为在代码path中没有执行实际的asynchronous代码)。 与FooAsync(false).ConfigureAwait(false);相同的调用FooAsync(false).ConfigureAwait(false); 会导致执行后跳转到线程池线程,但是。

这里是基于挖掘.NET Reference Source的这个行为的解释。

如果使用ConfigureAwait(true) ,则继续是通过使用SynchronizationContextTaskScheduler TaskSchedulerAwaitTaskContinuation来完成的,在这种情况下,一切都是清楚的。

如果使用ConfigureAwait(false) (或者如果没有同步上下文来捕获),则通过AwaitTaskContinuation来完成, AwaitTaskContinuation尝试首先内联继续任务,然后使用ThreadPool将其排队,如果内联不可行的话。

内联由IsValidLocationForInlining确定,它不会在具有自定义同步上下文的线程上内联任务。 但是,它最好在当前的池线程上将其内联。 这就解释了为什么我们在第一种情况下被推到一个池线程上,并且在第二种情况下(和Task.Delay(100) )停留在同一个线程池中。

我觉得用稍微不同的方式来思考这个问题是最容易的。

假设你有:

 await task.ConfigureAwait(false); 

首先,如果task已经完成,那么Reed指出, ConfigureAwait实际上被忽略,执行继续(在同一个线程中同步)。

否则, await将暂停该方法。 在这种情况下,当await恢复,并且看到ConfigureAwaitfalse ,有特殊的逻辑来检查代码是否有一个SynchronizationContext并在线程池上恢复(如果是这种情况)。 这是无证的,但不是不正确的行为。 因为没有证件,所以我build议你不要依赖这个行为。 如果你想在线程池上运行一些东西,使用Task.RunConfigureAwait(false)字面意思是“我不在乎这个方法在什么情况下恢复”。

请注意, ConfigureAwait(true) (默认值)将继续当前SynchronizationContext TaskScheduler上的方法。 虽然ConfigureAwait(false)将在除了带有SynchronizationContext线程之外的任何线程上继续该方法。 他们并不完全相反。