スレッド機能の移行

このトピックでは、ユニバーサル Windows プラットフォーム (UWP) アプリケーションのスレッド コードをWindows アプリ SDKに移行する方法について説明します。

API と機能の違いの概要

UWP のスレッド モデルは、アプリケーション STA (ASTA) と呼ばれるシングル スレッド アパートメント (STA) モデルのバリエーションであり、再入をブロックし、さまざまな再入バグやデッドロックを回避するのに役立ちます。 ASTA スレッドは、UI スレッドとも呼ばれます。

Windows アプリ SDKでは、同じ再入セーフガードを提供しない標準の STA スレッド モデルが使用されます。

CoreDispatcher 型は DispatcherQueue に移行されます。 そして、CoreDispatcher.RunAsync メソッドは DispatcherQueue.TryEnqueue に移行されます。

C++/WinRTwinrt::resume_foregroundCoreDispatcher と一緒に使用している場合は、これを代わりに DispatcherQueue を使用するように移行します。

ASTA から STA へのスレッド モデル

ASTA スレッド モデルの詳細については、ブログ記事「 Application STA について特別なものは何ですか?」を参照してください。

Windows アプリ SDKの STA スレッド モデルには再入の問題の防止に関する同じ保証がないため、UWP アプリが ASTA スレッド モデルの再入不可の動作を想定している場合、コードが期待どおりに動作しない可能性があります。

注目すべき 1 つの点は、XAML コントロールに再入することです (「UWP Photo Editor サンプル アプリの Windows App SDK への移行 (C++/WinRT)」のサンプルを参照してください)。 また、アクセス違反などの一部のクラッシュについては、使用すべき適切なスタックは、通常は直接クラッシュ呼び出し履歴です。 ただし、例外コードが 0xc000027b である "Stowed 例外" クラッシュが発生する場合は、適切な呼び出し履歴を取得するためにより多くの作業が必要になります。

Stowed 例外

Stowed 例外クラッシュでは、考えられるエラーが他の記録媒体に保存され、コードの一部で例外が処理されない場合に後で使用されます。 XAML では、エラーがすぐに致命的であると判断される場合があり、この場合は直接クラッシュ スタックが適切な場合があります。 しかし多くの場合、スタックは致命的であると判断される前にアンワインドされています。 Stowed 例外の詳細については、Inside Show のエピソード「Stowed 例外 C000027B」を参照してください。

Stowed 例外クラッシュの場合 (入れ子になったメッセージ ポンプを表示するために、またはスローされている XAML コントロールの固有の例外を確認するために)、Windows デバッガー (WinDbg) でクラッシュ ダンプを読み込み (「Windows 向けデバッグ ツールのダウンロード」を参照)、!pde.dse を使用して、Stowed 例外をダンプすることができます。

PDE デバッガー拡張機能 (!pde.dse コマンドについて) は、OneDrive から PDE*.zip ファイルをダウンロードすることによって入手できます。 その zip ファイルの適切な x64 または x86 の .dll を、WinDbg インストールの winext ディレクトリに配置します。これにより、!pde.dse によって Stowed 例外クラッシュ ダンプが処理されます。

多くの場合、複数の Stowed 例外が発生し、最後に処理されるか無視されます。 最も一般的なのは、最初の Stowed 例外に興味があります。 場合によっては、最初の Stowed 例外として、2 番目の例外が再スローされることがあります。そのため、2 番目の例外が 1 つ目のスタックよりも詳細に表示されている場合は、2 番目の例外がエラーの発生源である可能性があります。 個々の Stowed 例外に関連付けられたエラー コードも有益です。これは、各例外に関連付けられている HRESULT が提供されるためです。

Windows.UI.Core.CoreDispatcher を Microsoft.UI.Dispatching.DispatcherQueue に変更する

このセクションは、UWP アプリで Windows.UI.Core.CoreDispatcher クラスを使用している場合に該当します。 これには、DependencyObject.DispatcherCoreWindow.Dispatcher などの CoreDispatcher を取得または返すメソッドまたはプロパティの使用が含まれます。 たとえば、Windows.UI.Xaml.Controls.Page に属する CoreDispatcher を取得するときに、DependencyObject.Dispatcher を呼び出します。

// MainPage.xaml.cs in a UWP app
if (this.Dispatcher.HasThreadAccess)
{
    ...
}
// MainPage.xaml.cpp in a UWP app
if (this->Dispatcher().HasThreadAccess())
{
    ...
}

代わりに、Windows App SDK アプリでは、 Microsoft.UI.Dispatching.DispatcherQueue クラスを使用する必要があります。 また、DispatcherQueue を取得または返す、対応するメソッドまたはプロパティ (DependencyObject.DispatcherQueue プロパティや Microsoft.UI.Xaml.Window.DispatcherQueue プロパティなど) があります。 たとえば、Microsoft.UI.Xaml.Controls.Page に属する DispatcherQueue を取得するときに DependencyObject.DispatcherQueue を呼び出すとします (ほとんどの XAML オブジェクトは DependencyObject です)。

// MainPage.xaml.cs in a Windows App SDK app
if (this.DispatcherQueue.HasThreadAccess)
{
    ...
}
// MainPage.xaml.cpp in a Windows App SDK app
#include <winrt/Microsoft.UI.Dispatching.h>
...
if (this->DispatcherQueue().HasThreadAccess())
{
    ...
}

CoreDispatcher.RunAsync to DispatcherQueue.TryEnqueue を変更する

このセクションは、Windows.UI.Core.CoreDispatcher.RunAsync メソッドを使用して、メイン UI スレッド (または特定の Windows.UI.Core.CoreDispatcher に関連付けられたスレッド) で実行するタスクをスケジュールする場合に該当します。

// MainPage.xaml.cs in a UWP app
public void NotifyUser(string strMessage)
{
    if (this.Dispatcher.HasThreadAccess)
    {
        StatusBlock.Text = strMessage;
    }
    else
    {
        var task = this.Dispatcher.RunAsync(
            Windows.UI.Core.CoreDispatcherPriority.Normal,
            () => StatusBlock.Text = strMessage);
    }
}
// MainPage.cpp in a UWP app
void MainPage::NotifyUser(std::wstring strMessage)
{
    if (this->Dispatcher().HasThreadAccess())
    {
        StatusBlock().Text(strMessage);
    }
    else
    {
        auto task = this->Dispatcher().RunAsync(
            Windows::UI::Core::CoreDispatcherPriority::Normal,
            [strMessage, this]()
            {
                StatusBlock().Text(strMessage);
            });
    }
}

Windows App SDK アプリでは、Microsoft.UI.Dispatching.DispatcherQueue.TryEnqueue メソッドを使用します。 Microsoft.UI.Dispatching.DispatcherQueue は、DispatcherQueue に関連付けられているスレッドで実行されるタスクに追加されます。

// MainPage.xaml.cs in a Windows App SDK app
public void NotifyUser(string strMessage)
{
    if (this.DispatcherQueue.HasThreadAccess)
    {
        StatusBlock.Text = strMessage;
    }
    else
    {
        bool isQueued = this.DispatcherQueue.TryEnqueue(
        Microsoft.UI.Dispatching.DispatcherQueuePriority.Normal,
        () => StatusBlock.Text = strMessage);
    }
}
// MainPage.xaml.cpp in a Windows App SDK app
#include <winrt/Microsoft.UI.Dispatching.h>
...
void MainPage::NotifyUser(std::wstring strMessage)
{
    if (this->DispatcherQueue().HasThreadAccess())
    {
        StatusBlock().Text(strMessage);
    }
    else
    {
        bool isQueued = this->DispatcherQueue().TryEnqueue(
            Microsoft::UI::Dispatching::DispatcherQueuePriority::Normal,
            [strMessage, this]()
            {
                StatusBlock().Text(strMessage);
            });
    }
}

Migrate winrt::resume_foreground (C++/WinRT)

このセクションは、C++/WinRT UWP アプリのコルーチンで winrt::resume_foreground 関数を使用する場合に適用されます。

UWP では、winrt::resume_foreground のユースケースは、実行をフォアグラウンド スレッドに切り替えることです (フォアグラウンド スレッドは多くの場合、Windows.UI.Core.CoreDispatcher に関連付けられているものです)。 以下に、表の例を示します。

// MainPage.cpp in a UWP app
winrt::fire_and_forget MainPage::ClickHandler(IInspectable const&, RoutedEventArgs const&)
{
    ...
    co_await winrt::resume_foreground(this->Dispatcher());
    ...
}

Windows App SDK アプリで次のようにします。

したがって、最初に Microsoft.Windows.ImplementationLibrary NuGet パッケージへの参照を追加します。

次に、以下の include をターゲット プロジェクトの pch.h に追加します。

#include <wil/cppwinrt_helpers.h>

次に、次に示すパターンに従います。

// MainPage.xaml.cpp in a Windows App SDK app
...
winrt::fire_and_forget MainPage::ClickHandler(IInspectable const&, RoutedEventArgs const&)
{
    ...
    co_await wil::resume_foreground(this->DispatcherQueue());
    ...
}