[英]ConfigureAwait(false) - Does the continuation always run on different thread?
[英]ConfigureAwait pushes the continuation to a pool thread
這是一些 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
繼續推送到這里的池線程?
我用“推到池中的線程”在這里當主延續回調(的描述的情況下action
參數TaskAwaiter.UnsafeOnCompleted
被調用在一個線程,而二次回調(一傳給ConfiguredTaskAwaiter.UnsafeOnCompleted
)排隊到一個池線程。
文檔說:
continueOnCapturedContext ... true 嘗試將延續編組回捕獲的原始上下文; 否則為假。
我知道當前線程上安裝了WinFormsSynchronizationContext
。 盡管如此,沒有嘗試進行編組,執行點已經存在。
因此,它更像是“永遠不要繼續捕獲的原始上下文” ......
正如預期的那樣,如果執行點已經在沒有同步上下文的池線程上,則沒有線程切換:
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 }
已更新,再進行一項測試以查看是否有任何同步。 上下文不足以繼續(而不是原始的)。 確實是這樣:
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
”。
如果您不捕獲現有上下文,則處理該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);
您將看到您停留在主線程上(假設它是等待之前的當前上下文),因為在代碼路徑中沒有實際執行的異步代碼。 與FooAsync(false).ConfigureAwait(false);
相同的調用FooAsync(false).ConfigureAwait(false);
但是,會導致它在執行后跳轉到線程池線程。
這是基於挖掘.NET Reference Source對這種行為的解釋。
如果使用ConfigureAwait(true)
,則繼續通過使用SynchronizationContextTaskScheduler
TaskSchedulerAwaitTaskContinuation
完成,在這種情況下一切都很清楚。
如果使用ConfigureAwait(false)
(或者如果沒有同步上下文要捕獲),則通過AwaitTaskContinuation
完成,它首先嘗試內聯延續任務,然后在無法內聯時使用ThreadPool
將其排隊。
內聯由IsValidLocationForInlining
確定,它從不使用自定義同步上下文將任務內聯到線程上。 然而,它最好將它內聯到當前池線程上。 這解釋了為什么我們在第一種情況下被推送到池線程上,而在第二種情況下(使用Task.Delay(100)
)保持在同一個池線程上。
我認為最容易以稍微不同的方式思考這一點。
假設你有:
await task.ConfigureAwait(false);
首先,如果task
已經完成,那么正如 Reed 指出的那樣,實際上會忽略ConfigureAwait
並繼續執行(同步,在同一線程上)。
否則, await
將暫停該方法。 在這種情況下,當await
恢復並看到ConfigureAwait
為false
,有特殊的邏輯來檢查代碼是否具有SynchronizationContext
,如果是,則在線程池上恢復。 這是無證但並非不當行為。 因為它是無證的,我建議你不要依賴於行為; 如果你想在線程池上運行一些東西,使用Task.Run
。 ConfigureAwait(false)
字面意思是“我不在乎這個方法在什么上下文中恢復。”
請注意, ConfigureAwait(true)
(默認值)將在當前SynchronizationContext
或TaskScheduler
上繼續該方法。 雖然ConfigureAwait(false)
將在任何線程上繼續該方法,除了具有SynchronizationContext
線程。 它們並不完全相反。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.