为啥不等待 Task.Run() 同步回 UI 线程/原始上下文?
Posted
技术标签:
【中文标题】为啥不等待 Task.Run() 同步回 UI 线程/原始上下文?【英文标题】:Why doesn't await Task.Run() sync back to UI Thread / origin context?为什么不等待 Task.Run() 同步回 UI 线程/原始上下文? 【发布时间】:2020-04-01 04:20:15 【问题描述】:我以为我理解了 async-wait 模式 和 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)上继续。但事实并非如此。
为什么?
【问题讨论】:
@SushantYelpale 不正确 【参考方案1】:这是一件棘手的事情。
您在 UI 线程上调用 await
,这是真的。但!你是在App
的构造函数中做的。
请记住,隐式生成的启动代码如下所示:
public static void Main()
var app = new YourNamespace.App();
app.InitializeComponent();
app.Run();
用于返回主线程的事件循环仅作为Run
执行的一部分启动。所以在App
构造函数运行期间,没有事件循环。然而。
因此,SynchronizationContext
,在技术上负责在await
之后将流返回到主线程,在 App 的构造函数中是 null
。
(SynchronizationContext
被 await
捕获 before 等待,所以 after 完成 Task
没有关系已经有一个有效的 @987654332 @:捕获的值为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
,它将按照您的预期运行。
【讨论】:
以上是关于为啥不等待 Task.Run() 同步回 UI 线程/原始上下文?的主要内容,如果未能解决你的问题,请参考以下文章
在同步方法中运行没有 Wait() 的 Task.Run() 有啥影响?
使用异步方法等待 Task.Run 不会在正确的线程上引发异常
CS0121'Task.Run之间的调用不明确 (Func键 )'和'Task.Run(Func )”
为啥通过调用Task.Run和ThreadPool.QueueUserWorkItem排队到ThreadPool时线程数增加了不止一个?