为什么新线程可以访问UI?

Why can new threads access the UI?

以下面的方法为例:

private async void F()
{
    button.IsEnabled = false;
    await Task.Delay(1000);
    button.IsEnabled = true;
}

在这种情况下,任何从 await 开始的代码总是出现在另一个线程上(编辑:错误),这可能不应该访问 UI 线程,类似于桌面应用程序。在类似的情况下,我记得有一个例外,例如:

The application called an interface that was marshalled for a different thread.

但是,该示例没有触发任何异常。这是预期的吗?我能可靠地编写这样的代码吗?

UI 线程上的代码 运行ning 有一个 SynchronizationContext。您可以通过打印 SynchronizationContext.Current 看到这一点。在你等待上下文被捕获之前,等待你的代码在那个上下文上恢复,这确保 UI 线程上的延续 运行s。

要获得您正在引用的行为,其中延续是 ThreadPool 线程上的 运行,您可以使用 ConfigureAwait(false) 禁用 SynchronizationContext 捕获:

private async void FooAsync()
{
    button.IsEnabled = false;
    await Task.Delay(1000).ConfigureAwait(false);
    button.IsEnabled = true;
}

此代码将引发您预期的异常。

Is this expected? Can I reliably write code like this?

是的,是的。默认情况下,使用 async-await 的代码将 "do the right thing"。但是如果你确实想将某些东西卸载到 ThreadPool 线程,你可以使用 Task.Run.

any code starting at await always occurs on another thread (non-UI thread, right?),

不,一点也不。 await 不启动其他线程。我有一个 async intro 如果你觉得这个陈述令人困惑,它可能会有所帮助。

await做的是将方法的其余部分安排为异步操作完成后运行的延续(在这种情况下,异步操作只是一个定时器触发)。默认情况下,await 将捕获一个 "context",即 SynchronizationContext.Current(或者,如果是 null,则上下文是 TaskScheduler.Current)。在这种情况下,有一个 UI SynchronizationContext 确保 async 方法的其余部分将 运行 在 UI 线程上。