[英]Why doesn't await Task.Run() sync back to UI Thread / origin context?
我以為我了解異步等待模式和Task.Run
操作。
但我想知道為什么在下面的代碼示例中,從完成的任務返回后, await
沒有同步回 UI 線程。
public async Task InitializeAsync()
{
Console.WriteLine($"Thread: {Thread.CurrentThread.ManagedThreadId}"); // "Thread: 1"
double value = await Task.Run(() =>
{
Console.WriteLine($"Thread: {Thread.CurrentThread.ManagedThreadId}"); // Thread: 6
// Do some CPU expensive stuff
double x = 42;
for (int i = 0; i < 100000000; i++)
{
x += i - Math.PI;
}
return x;
}).ConfigureAwait(true);
Console.WriteLine($"Result: {value}");
Console.WriteLine($"Thread: {Thread.CurrentThread.ManagedThreadId}"); // Thread: 6 - WHY??
}
此代碼在帶有附加 Visual Studio 2019 調試器的 Windows 10 系統上的 .NET Framework WPF 應用程序中運行。
我從我的App
類的構造函數中調用此代碼。
public App()
{
this.InitializeAsync().ConfigureAwait(true);
}
也許這不是最好的方法,但我不確定這是否是奇怪行為的原因。
代碼從 UI 線程開始,應該執行一些任務。 通過await
操作和任務完成后的ConfigureAwait(true)
,它應該在主線程 (1) 上繼續。 但事實並非如此。
為什么?
這是一件棘手的事情。
您正在 UI 線程上調用await
,這是真的。 但! 您正在App
的構造函數中執行此操作。
請記住,隱式生成的啟動代碼如下所示:
public static void Main()
{
var app = new YourNamespace.App();
app.InitializeComponent();
app.Run();
}
用於返回主線程的事件循環僅作為Run
執行的一部分Run
。 所以在App
構造函數運行期間,沒有事件循環。 然而。
因此,在技術上負責在await
之后將流返回到主線程的SynchronizationContext
在 App 的構造函數中為null
。
( SynchronizationContext
在await
之前被await
捕獲,所以在完成Task
后已經有一個有效的SynchronizationContext
並不重要:捕獲的值為null
,因此await
繼續在線程池線程上執行。)
所以問題不在於您在構造函數中運行代碼,問題在於您在App
的構造函數中運行它,此時應用程序尚未完全設置好以供執行。 MainWindow
的構造函數中的相同代碼將表現良好。
讓我們做一些實驗:
public App()
{
Console.WriteLine($"sc = {SynchronizationContext.Current?.ToString() ?? "null"}");
}
protected override void OnStartup(StartupEventArgs e)
{
Console.WriteLine($"sc = {SynchronizationContext.Current?.ToString() ?? "null"}");
base.OnStartup(e);
}
第一個輸出給出
sc = null
第二
sc = System.Windows.Threading.DispatcherSynchronizationContext
所以你可以看到在OnStartup
中已經有一個同步上下文。 因此,如果您將InitializeAsync()
移動到OnStartup
,它將按照您的預期運行。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.