我以为我了解异步等待模式和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)上继续进行。但事实并非如此。
为什么?
答案 0 :(得分:10)
这是一件棘手的事情。
您正在UI线程上调用await
,这是事实。但!您正在App
的构造函数中完成此操作。
请记住,隐式生成的启动代码如下:
public static void Main()
{
var app = new YourNamespace.App();
app.InitializeComponent();
app.Run();
}
用于返回主线程的事件循环仅在执行Run
时才启动。因此,在App
构造函数运行期间,没有事件循环。
因此,在技术上负责SynchronizationContext
之后将流返回到主线程的await
在应用程序的构造函数中为null
。
({SynchronizationContext
由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
中,它的行为将与您期望的一样。