【问题标题】:Why can new threads access the UI?为什么新线程可以访问 UI?
【发布时间】:2016-05-08 11:05:43
【问题描述】:

以下面的方法为例:

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

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

应用程序调用了一个为不同线程编组的接口。

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

【问题讨论】:

    标签: c# windows-runtime windows-store-apps async-await


    【解决方案1】:

    任何从 await 开始的代码总是发生在另一个线程(非 UI 线程,对吗?),

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

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

    【讨论】:

    • 可以肯定的是,Task.Delay 不是开始一个新线程吗?我记得关于 SO 的一个帖子,有人这么争论。
    • @Avenicci:不。它只是启动一个计时器。上次我检查时,每个应用程序都有一个线程可以一起处理所有计时器。并且当计时器完成时,它的回调在线程池线程上调用,该线程非常简单地用于完成从Task.Delay 返回的Task。但没有创建新线程。
    • 如果Task.Delay使用同一个线程,异步方法如何直接终止?
    • @Avenicci:调用线程不会终止 - 它只是返回。
    【解决方案2】:

    在 UI 线程上运行的代码有一个 SynchronizationContext。您可以通过打印SynchronizationContext.Current 来查看。在您等待捕获上下文之前,在等待您的代码在该上下文上恢复之后,确保继续在 UI 线程上运行。

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

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

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

    这是预期的吗?我可以可靠地编写这样的代码吗?

    是的,是的。默认情况下,使用 async-await 的代码将“做正确的事”。但是,如果您确实想将某些内容卸载到 ThreadPool 线程,您可以使用 Task.Run。

    【讨论】:

    • 我想我弄错了,await 之外的代码在不同的线程上运行,所以它被发布到 UI 线程使用的SynchronizationContext?
    • @Avenicci Task.Delay 完成后的延续被发布到确保它在 UI 线程上运行的 SC。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-05
    • 2012-07-31
    • 1970-01-01
    • 1970-01-01
    • 2015-06-02
    • 1970-01-01
    相关资源
    最近更新 更多