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
恢复,并且看到ConfigureAwait
为false
,有特殊的逻辑来检查代码是否有一个SynchronizationContext
并在线程池上恢复(如果是这种情况)。 这是无证的,但不是不正确的行为。 因为没有证件,所以我build议你不要依赖这个行为。 如果你想在线程池上运行一些东西,使用Task.Run
。 ConfigureAwait(false)
字面意思是“我不在乎这个方法在什么情况下恢复”。
请注意, ConfigureAwait(true)
(默认值)将继续当前SynchronizationContext
或 TaskScheduler
上的方法。 虽然ConfigureAwait(false)
将在除了带有SynchronizationContext
线程之外的任何线程上继续该方法。 他们并不完全相反。