保持 UI 线程有响应

用户期望你的应用在执行计算时保持响应状态,而不管计算机类型如何。 这意味着不同应用的不同内容 - 提供逼真的物理、从磁盘或 Web 加载数据、呈现复杂场景、在页面之间导航或处理数据。 无论计算类型如何,用户都希望你的应用对其输入执行操作,并消除其出现无响应的实例。

应用是事件驱动的:代码在响应事件时执行工作,然后在下一个事件之前处于空闲状态。 UI 的平台代码(布局、输入、引发事件)和应用的 UI 代码均在同一 UI 线程上运行。 一次只执行一个指令,因此,如果应用代码处理事件需要太长的时间,框架将无法运行布局或引发新事件。 应用的响应能力与处理工作的 UI 线程的可用性相关。

需要使用 UI 线程对 UI 元素进行几乎所有的更改。 无法从后台线程更新 UI,但可以使用 DispatcherQueue.TryEnqueue 将消息发布到该 UI,以导致代码在那里运行。

Important

在 WinUI 3 桌面应用中,使用 DispatcherQueue 而不是 UWP CoreDispatcher。 通过窗口的 DispatcherQueue 属性或 DispatcherQueue.GetForCurrentThread() 访问调度程序队列。

注释

单独的呈现线程可以应用不影响输入处理方式或基本布局的 UI 更改。 例如,许多不影响布局的动画和过渡都可以在此呈现线程上运行。

延迟元素的实例化

应用中一些最慢的阶段包括启动和切换视图。 不要为了呈现用户初始看到的界面而做超出必要范围的工作。 例如,不要为渐进式披露的内容或弹出内容创建 UI。

  • 使用 x:Load 属性 延迟实例化元素。
  • 以编程方式将元素插入到按需树中。

以低优先级使用 DispatcherQueue.TryEnqueue,以便在 UI 线程空闲时将工作加入队列进行处理。

使用异步 API

若要使应用保持响应,请在 API 可用时使用异步版本的 API。 异步 API 可确保当前执行线程不会被阻塞较长时间。 从 UI 线程调用 API 时,请始终使用异步版本(如果存在)。

将工作卸载到后台线程

编写事件处理程序以快速返回。 当需要执行较大量的工作时,请将其放到后台线程中执行,然后返回。

你可以通过在 C# 中使用 await 运算符来异步安排工作。 但是, await 不保证工作在后台线程上运行。 许多 Windows 应用 SDK API 会为你将工作安排到后台线程上执行,但如果你仅使用 await 来调用应用代码,该方法就会在 UI 线程上运行。 必须在后台线程上显式运行应用代码。 在 C# 中,通过将代码传递给 Task.Run 来实现此目的。

请记住,只能从 UI 线程访问 UI 元素。 在启动后台工作之前,使用 UI 线程访问 UI 元素,或使用 DispatcherQueue.TryEnqueue 后台线程将 UI 更新发布回 UI 线程。

public sealed partial class MainWindow : Window
{
    // Declared in MainWindow.xaml as x:Name="statusText".
    private TextBlock statusText;

    private async void NextMove_Click(object sender, RoutedEventArgs e)
    {
        // The await causes the handler to return immediately.
        await Task.Run(() => ComputeNextMove());

        // Now update the UI with the results.
        // This code runs on the UI thread after ComputeNextMove completes.
        statusText.Text = "Move computed.";
    }

    private void ComputeNextMove()
    {
        // Perform background work here.
        // Don't directly access UI elements from this method.
    }
}

在此示例中,NextMove_Click 处理程序在 await 处返回,从而使 UI 线程保持响应。 在该处理程序中,ComputeNextMove(它在后台线程上运行)完成后,执行会再次继续,其余代码会使用结果更新 UI。