UI スレッドの応答性の確保

ユーザーは、コンピューターの種類に関係なく、計算を実行しながらアプリの応答性を維持することを期待しています。 つまり、現実的な物理演算の提供、ディスクまたは Web からのデータの読み込み、複雑なシーンの表示、ページ間の移動、データの処理など、さまざまなアプリでさまざまなことを意味します。 計算の種類に関係なく、ユーザーはアプリが入力に対して動作し、応答していないと思われるインスタンスを排除したいと考えています。

アプリはイベント ドリブンです。コードはイベントに応答して作業を実行し、次のイベントまでアイドル状態になります。 UI のプラットフォーム コード (レイアウト、入力、イベントの発生) と UI 用のアプリのコードはすべて、同じ UI スレッドで実行されます。 そのスレッドで一度に実行される命令は 1 つだけであるため、アプリ コードでイベントの処理に時間がかかりすぎる場合、フレームワークはレイアウトを実行したり、新しいイベントを発生させたりすることはできません。 アプリの応答性は、UI スレッドを使用して作業を処理できるかどうかに関連します。

UI 要素のほぼすべての変更を行うには、UI スレッドを使用する必要があります。 バックグラウンド スレッドから UI を更新することはできませんが、 DispatcherQueue.TryEnqueue でメッセージを投稿して、コードをそこで実行させることができます。

Important

WinUI 3 デスクトップ アプリでは、UWP DispatcherQueueの代わりにCoreDispatcherを使用します。 ウィンドウの DispatcherQueue プロパティまたは DispatcherQueue.GetForCurrentThread()を使用してディスパッチャー キューにアクセスします。

Note

個別のレンダリング スレッドでは、入力の処理方法や基本的なレイアウトに影響しない UI の変更を適用できます。 たとえば、レイアウトに影響しない多くのアニメーションや画面切り替えは、このレンダリング スレッドで実行できます。

要素のインスタンス化を遅延

アプリの最も遅いステージには、起動ビューと切り替えビューなどがあります。 ユーザーが最初に表示する UI を表示するために必要以上の作業を行わないでください。 たとえば、段階的に公開されるコンテンツやポップアップ コンテンツの UI は作成しないでください。

  • 要素を遅延インスタンス化するには、 x:Load 属性 を使用します。
  • プログラムによって要素をツリーにオンデマンドで挿入します。

優先順位の低い DispatcherQueue.TryEnqueue を使用して、UI スレッドがビジー状態でないときに処理する作業をキューに入れます。

非同期 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.
    }
}

この例では、ui スレッドの応答性を維持するために、 NextMove_Click ハンドラーが await で返されます。 実行は、(バックグラウンド スレッドで実行される) ComputeNextMove が完了すると、そのハンドラーで再開され、残りのコードによって結果を使って UI が更新されます。