スレッド機能の移行

このトピックでは、ユニバーサル 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 スレッド モデルの詳細については、ブログ記事「 アプリケーション STA について特別なものは何ですか?を参照してください。

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

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

格納された例外

格納例外クラッシュでは、発生した可能性のあるエラーが保存され、コードのどの部分でも例外が処理されなかった場合に、その情報が後で使用されます。 XAML では、エラーがすぐに致命的であると判断される場合があり、この場合は直接クラッシュ スタックが適切な場合があります。 しかし実際には、致命的だと判断される前にスタックが巻き戻されていることの方が多いです。 格納例外の詳細については、Inside Show のエピソード「Stowed Exception C000027B」を参照してください。

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

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

多くの場合、複数の格納された例外が存在し、そのうち末尾のものは処理済みであるか、無視されています。 通常は、最初の格納された例外が重要です。 場合によっては、最初の stowed 例外は 2 番目の例外の再スローであることがあります。そのため、2 番目の stowed 例外が最初の例外と同じスタック内でより深い位置まで示している場合は、2 番目の例外がエラーの起点である可能性があります。 各格納例外とともに表示されるエラー コードも有益です。これは、その例外に関連付けられた 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 アプリ 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 アプリ 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);
            });
    }
}

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 アプリ 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());
    ...
}

参照