Поддерживайте отклик потока пользовательского интерфейса

Пользователи ожидают, что ваше приложение будет реагировать на выполнение вычислений независимо от типа компьютера. Это означает, что разные вещи для различных приложений — обеспечение реалистичной физики, загрузка данных с диска или в Интернете, представление сложных сцен, навигация между страницами или обработка данных. Независимо от типа вычислений пользователи хотят, чтобы ваше приложение реагировало на их действия и не допускало ситуаций, когда оно кажется неотзывчивым.

Ваше приложение управляется событиями: код выполняет работу в ответ на событие, а затем находится в состоянии простоя до следующего события. Код платформы для пользовательского интерфейса (макет, входные данные, повышение событий) и код приложения для пользовательского интерфейса выполняются в одном потоке пользовательского интерфейса. Только одна инструкция выполняется в этом потоке одновременно, поэтому если код приложения занимает слишком много времени для обработки события, платформа не может запускать макет или создавать новые события. Скорость реагирования приложения связана с доступностью потока пользовательского интерфейса для обработки работы.

Для внесения почти всех изменений в элементы пользовательского интерфейса необходимо использовать поток пользовательского интерфейса. Вы не можете обновлять интерфейс пользователя из фонового потока, но можете отправить в него сообщение с помощью DispatcherQueue.TryEnqueue, чтобы выполнить там код.

Important

В классических приложениях WinUI 3 используйте DispatcherQueue вместо UWP CoreDispatcher. Доступ к очереди диспетчера через свойство окна DispatcherQueue или DispatcherQueue.GetForCurrentThread().

Note

Отдельный поток отрисовки может применять изменения пользовательского интерфейса, которые не влияют на обработку входных данных или базовый макет. Например, многие анимации и переходы, которые не влияют на макет, могут выполняться в этом потоке отрисовки.

Отложенное создание экземпляра элемента

Некоторые из самых медленных этапов в приложении включают запуск и переключение представлений. Не делайте больше работы, чем необходимо, чтобы открыть пользовательский интерфейс, который пользователь видит изначально. Например, не создавайте пользовательский интерфейс для постепенно раскрываемого содержимого или содержимого всплывающего окна.

  • Используйте атрибут x:Load, чтобы отложенно создавать экземпляры элементов.
  • Программно вставлять элементы в дерево по мере необходимости.

Используйте DispatcherQueue.TryEnqueue с низким приоритетом, чтобы поместить задачу в очередь на обработку потоком пользовательского интерфейса, когда он не занят.

Использование асинхронных API

Чтобы обеспечить реагирование приложения, используйте асинхронные версии API, когда они доступны. Асинхронный API гарантирует, что активный поток выполнения никогда не блокируется в течение значительного времени. При вызове API из потока пользовательского интерфейса всегда используйте асинхронную версию, если она существует.

Перенесите выполнение в фоновые потоки

Напишите обработчики событий, чтобы быстро вернуться. Если требуется выполнить значительный объём работы, запустите её в фоновом потоке и вернитесь.

Вы можете асинхронно планировать работу с помощью await оператора в C#. Однако await не гарантирует, что работа выполняется в фоновом потоке. Многие API Windows App SDK сами планируют выполнение работы в фоновом потоке, но если вы вызываете код приложения, используя только await, этот метод выполняется в потоке пользовательского интерфейса. Необходимо явным образом выполнять код приложения в фоновом потоке. В C#выполните это, передав код в Task.Run.

Помните, что доступ к элементам пользовательского интерфейса можно получить только из потока пользовательского интерфейса. Используйте поток 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, чтобы поток пользовательского интерфейса оставался отзывчивым. Выполнение в этом обработчике снова возобновляется после завершения ComputeNextMove (который выполняется в фоновом потоке), а оставшийся код обновляет пользовательский интерфейс, отображая результаты.