DispatcherQueue

ハイライト

  • Windows アプリ SDKの DispatcherQueue クラスは、スレッドのタスクがシリアル形式で実行される優先順位付きキューを管理します。
  • これは、バックグラウンド スレッドが DispatcherQueue のスレッド (スレッド アフィニティを持つオブジェクトがライブである UI スレッドなど) でコードを実行するための手段を提供します。
  • このクラスは、任意のメッセージ ループと正確に統合されます。 たとえば、入れ子になったメッセージ ループの一般的な Win32 イディオムがサポートされています。
  • AppWindow クラスは DispatcherQueue と統合されます。特定のスレッドの DispatcherQueue がシャットダウンされると、AppWindow インスタンスは自動的に破棄されます。
  • タイムアウトの有効期限が切れたときに呼び出されるデリゲートを登録する手段を提供します。
  • メッセージ ループがいつ終了するかをコンポーネントに通知し、必要に応じて未処理の作業が完了するまでシャットダウンを延期するイベントを提供します。 これにより、 DispatcherQueue を使用するがメッセージ ループを所有していないコンポーネントは、ループの終了時にスレッド上でクリーンアップを実行できます。
  • DispatcherQueue はスレッド シングルトンです (指定されたスレッドで実行されているスレッドは、最大で 1 つです)。 既定では、スレッドには DispatcherQueue がありません。
  • スレッド所有者は、 DispatcherQueueController を作成してスレッドの DispatcherQueue を初期化できます。 その時点で、任意のコードがスレッドの DispatcherQueue にアクセスできます。ただし、 DispatcherQueueController の所有者のみが DispatcherQueueController.ShutdownQueue メソッドにアクセスでき、 DispatcherQueue をドレインし、 ShutdownStarting イベントと ShutdownCompleted イベントを発生させます。
  • 最も外側のメッセージ ループ所有者は 、DispatcherQueue インスタンスを作成する必要があります。 ディスパッチが完了したタイミングを認識するのは、スレッドの最も外側のメッセージ ループの実行を担当するコードだけです。これは 、DispatcherQueue をシャットダウンするのに適したタイミングです。 つまり、 DispatcherQueue に依存するコンポーネントは、スレッドのメッセージ ループを所有していない限り 、DispatcherQueue を作成しないでください。

スレッド モデルを選択する

スレッドとそのメッセージ ループを所有するユーザーに基づいて DispatcherQueueController を作成する方法を選択します。

Method スレッドとイベント ループの動作
CreateOnDedicatedThread 新しいスレッドを作成し、そのスレッドで DispatcherQueue イベント ループを実行します。 このキューに対して RunEventLoop を呼び出さないでください。
CreateOnCurrentThread DispatcherQueue を呼び出し元のスレッドに関連付けます。 キューに置かれた作業とタイマーを実行できるように、コードで USER32 メッセージ ポンプを実行するか 、RunEventLoop を呼び出す必要があります。

たとえば、次の C++/WinRT コードは、新しい専用スレッドで繰り返しタイマーを作成します。 タイマーが必要な限り、コントローラーとタイマーを保持します。

using namespace winrt::Microsoft::UI::Dispatching;

DispatcherQueueController queueController{
    DispatcherQueueController::CreateOnDedicatedThread()
};
DispatcherQueueTimer timer{ queueController.DispatcherQueue().CreateTimer() };

timer.Interval(std::chrono::seconds{ 1 });
timer.Tick([](auto const&, auto const&)
{
    // This handler runs on the dedicated DispatcherQueue thread.
});
timer.Start();

専用スレッドのイベント ループが自動的に開始されます。 キューが不要になったら、タイマーを停止し、 ShutdownQueueAsync を呼び出します。

Important

この記事の例では、Microsoft.UI.Dispatching名前空間のWindows アプリ SDK型を使用します。 Win32 CreateDispatcherQueueController 関数は、個別の Windows.System.DispatcherQueueController 型を作成します。 2 つの API ファミリのオブジェクトを混在しないでください。

概要

スレッドは、イベント ループを終了した後、 DispatcherQueue をシャットダウンする必要があります。 これにより、ShutdownStarting イベントと ShutdownCompleted イベントが発生し、それ以上のエンキューを無効にする前に、最後に残っている保留中のエンキュー済み項目がすべて処理されます。

  • DispatcherQueue 所有のメッセージ ループを使用して専用スレッドで実行されている DispatcherQueue をシャットダウンするには、DispatcherQueueController.ShutdownQueueAsync メソッドを呼び出します。
  • アプリが任意のメッセージ ループ (XAML Islands など) を所有しているシナリオでは、同期 DispatcherQueueController.ShutdownQueue メソッドを呼び出します。 このメソッドはシャットダウン イベントを発生させ、呼び出し元スレッドで DispatcherQueue を同期的にドレインします。

DispatcherQueueController.ShutdownQueueAsync または DispatcherQueueController.ShutdownQueue を呼び出すと、発生するイベントの順序は次のようになります。

  • シャットダウンを開始しています。 処理するアプリを対象としています。
  • FrameworkShutdownStarting. フレームワークで処理することを想定しています。
  • FrameworkShutdownCompleted。 フレームワークで処理することを想定しています。
  • シャットダウンが完了しました。 処理するアプリを対象としています。

イベントはアプリケーション/フレームワーク カテゴリに分割されるため、順番にシャットダウンを実現できます。 つまり、フレームワークのシャットダウン イベントの前にアプリケーションのシャットダウンを明示的に上げることで、アプリケーションがダウンしてもフレームワーク コンポーネントが使用できない状態になる危険性はありません。

namespace winrt 
{
    using namespace Microsoft::UI::Dispatching;
}

// App runs its own custom message loop.
void RunCustomMessageLoop()
{
    // Create a DispatcherQueue.
    auto dispatcherQueueController{winrt::DispatcherQueueController::CreateOnCurrentThread()};

    // Run a custom message loop. Runs until the message loop owner decides to stop.
    MSG msg;
    while (GetMessage(&msg, nullptr, 0, 0))
    {
        if (!ContentPreTranslateMessage(&msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    // Run down the DispatcherQueue. This single call also runs down the system DispatcherQueue
    // if one was created via EnsureSystemDispatcherQueue:
    // 1. Raises DispatcherQueue.ShutdownStarting event.
    // 2. Drains remaining items in the DispatcherQueue, waits for deferrals.
    // 3. Raises DispatcherQueue.FrameworkShutdownStarting event.
    // 4. Drains remaining items in the DispatcherQueue, waits for deferrals.
    // 5. Disables further enqueuing.
    // 6. Raises the DispatcherQueue.FrameworkShutdownCompleted event.
    // 7. Raises the DispatcherQueue.ShutdownCompleted event.    

    dispatcherQueueController.ShutdownQueue();
}

最も外側および再帰的なメッセージ ループ

DispatcherQueue では、カスタム メッセージ ループがサポートされます。 ただし、カスタマイズを必要としない単純なアプリの場合は、既定の実装を提供します。 これにより、開発者の負担が軽減され、一貫して正しい動作が保証されます。

namespace winrt 
{
    using namespace Microsoft::UI::Dispatching;
}

// Simple app; doesn't need a custom message loop.
void RunMessageLoop()
{
    // Create a DispatcherQueue.
    auto dispatcherQueueController{winrt::DispatcherQueueController::CreateOnCurrentThread()};

    // Runs a message loop until a call to DispatcherQueue.EnqueueEventLoopExit or PostQuitMessage.
    dispatcherQueueController.DispatcherQueue().RunEventLoop();

    // Run down the DispatcherQueue. 
    dispatcherQueueController.ShutdownQueue();
}

// May be called while receiving a message.
void RunNestedLoop(winrt::DispatcherQueue dispatcherQueue)
{
    // Runs a message loop until a call to DispatcherQueue.EnqueueEventLoopExit or PostQuitMessage.
    dispatcherQueue.RunEventLoop();
}

// Called to break out of the message loop, returning from the RunEventLoop call lower down the
// stack.
void EndMessageLoop(winrt::DispatcherQueue dispatcherQueue)
{
    // Alternatively, calling Win32's PostQuitMessage has the same effect.
    dispatcherQueue.EnqueueEventLoopExit();
}

システム ディスパッチャーの管理

一部の Windows アプリ SDK コンポーネント(たとえば、MicaController)は、システム コンポーネントに依存しており、そのシステム コンポーネントでは、スレッド上で実行されているシステム DispatcherQueue(Windows.System.DispatcherQueue)が必要です。

このような場合、システム DispatcherQueue 依存関係を持つコンポーネントは EnsureSystemDispatcherQueue メソッドを呼び出し、アプリがシステム DispatcherQueue を管理できないようにします。

このメソッドが呼び出された場合、Windows アプリ SDK DispatcherQueue はシステム DispatcherQueue の有効期間を自動的に管理します。 システム DispatcherQueue を Windows アプリ SDK DispatcherQueue と共にシャットダウンします。 コンポーネントは、メッセージ ループの終了後に適切なクリーンアップを実行するために、Windows アプリ SDKとシステム DispatcherQueue シャットダウン イベントの両方に依存する場合があります。

namespace winrt 
{
    using namespace Microsoft::UI::Composition::SystemBackdrops;
    using namespace Microsoft::UI::Dispatching;
}

// The Windows App SDK component calls this during its startup.
void MicaControllerInitialize(winrt::DispatcherQueue dispatcherQueue)
{
    dispatcherQueue.EnsureSystemDispatcherQueue();

    // If the component needs the system DispatcherQueue explicitly, it can now grab it off the thread.
    winrt::Windows::System::DispatcherQueue systemDispatcherQueue =
        winrt::Windows::System::DispatcherQueue::GetForCurrentThread();
}

void AppInitialize()
{
    // App doesn't need to concern itself with the system DispatcherQueue dependency.
    auto micaController = winrt::MicaController();
}

AppWindow の統合

AppWindow クラスには DispatcherQueue と統合する機能があるため、DispatcherQueueController.ShutdownQueueAsync または DispatcherQueueController.ShutdownQueue メソッドが呼び出されたときに AppWindow オブジェクトを自動的に破棄できます。

呼び出し元が AppWindow に関連付けられている DispatcherQueue を取得できるようにする AppWindow のプロパティもあります。 コンポジション および 入力 名前空間内の他のオブジェクトと配置します。

DispatcherQueue を認識するには、AppWindow に明示的なオプトインが必要です。

namespace winrt 
{
    using namespace Microsoft::UI::Dispatching;
    using namespace Microsoft::UI::Windowing;
}

void Main()
{
    // Create a Windows App SDK DispatcherQueue.
    auto dispatcherQueueController{winrt::DispatcherQueueController::CreateOnCurrentThread()};

    auto appWindow = AppWindow::Create(nullptr, 0, dispatcherQueueController.DispatcherQueue());

    // Since we associated the DispatcherQueue above with the AppWindow, we're able to retrieve it 
    // as a property. If we were to not associate a dispatcher, this property would be null.
    ASSERT(appWindow.DispatcherQueue() == dispatcherQueueController.DispatcherQueue());

    // Runs a message loop until a call to DispatcherQueue.EnqueueEventLoopExit or PostQuitMessage.
    dispatcherQueueController.DispatcherQueue().RunEventLoop();

    // Rundown the Windows App SDK DispatcherQueue. While this call is in progress, the AppWindow.Destroyed
    // event will be raised since the AppWindow instance is associated with the DispatcherQueue.
    dispatcherQueueController.ShutdownQueue();
}

こちらも参照ください