June 2018

Volume 33 Number 6

C++ - コルーチンと C++/WinRT による効果的な非同期処理

によってKenny Kerr |年 6 月 2018

Windows ランタイムでは、比較的単純な非同期モデルを持つと同様に、Windows ランタイムで他のすべてという意味で、非同期メソッドを公開するコンポーネントを許可してこれらの非同期メソッドの呼び出しにアプリを簡単にフォーカスが移動します。それ自体でしていません同時実行ランタイムまたはビルド ブロックについても何も生成または非同期のメソッドを使用するためです。代わりに、すべてが、左個々 の言語のプロジェクションから。これは、する必要がありますであり、Windows ランタイムの非同期パターンを trivialize を意図したものではありません。このパターンを正しく実装小さな際立ってではありません。もちろん、こと、Windows ランタイムでの非同期の開発者の認識が非常に強く受けます任意の開発者の言語も意味します。開発者が使用することのみ C + + CX 可能性があります、たとえば、誤ってが当然と仮定する非同期ホット乱雑です。

C# 開発者の理想的な同時実行フレームワークは C++ 開発者にとって理想的な同時実行のライブラリから異なるになります。言語、および C++ の場合、ライブラリの役割は、非同期パターンのしくみを処理でき、言語固有の実装を自然なブリッジです。

コルーチンでは、両方を実装して、C++ では、非同期メソッドを呼び出すのための優先抽象型が、まず行いましょう非同期モデルの動作を理解すること。次のような単一の静的メソッドを使用して、クラスを考えます。

struct Sample
{
  Sample() = delete;
  static Windows::Foundation::IAsyncAction CopyAsync();
};

非同期メソッドの終わりに"Async"規則と考えることがこのコピーの非同期メソッドであるため。呼び出すことがコピー、ブロックしているか、同期の代替である可能性があります。考えられるバック グラウンド スレッドで使用するため、ブロックのコピー方法および応答しない状態で表示されるおそれがあるためをブロックすることのできない UI スレッドで使用するための非ブロッキング、または非同期のメソッドに対して、呼び出し元が必要ありますです。

最初は、CopyAsync メソッドを呼び出すが非常に簡単に見えるかもしれません。次の C++ コードを記述して可能性があります。

IAsyncAction async = Sample::CopyAsync();

ご想像のとおり、従来の手順に従って CopyAsync メソッドを呼び出すことの結果である場合でも、結果として得られる IAsyncAction、非同期のメソッドの実際に最終的な結果はありません。IAsyncAction は、同期または非同期で、状況に応じて、その結果まで待機する、呼び出し元が使用できるオブジェクトです。IAsyncAction、と共に、同様のパターンに従うし、呼び出し先、呼び出し元に情報を伝達するためのさまざまな機能を提供する 3 つの他のよく知られているインターフェイスがあります。内のテーブル図 1 4 つの非同期インターフェイスの比較を提供します。

非同期インターフェイスの図 1 の比較

名前 結果 進行状況
IAsyncAction No No
IAsyncActionWithProgress No Yes
IAsyncOperation Yes No
IAsyncOperationWithProgress Yes Yes

C++ には、ように、インターフェイスは表すことが図 2です。

図 2 C++ の用語で表現される非同期インターフェイス

namespace Windows::Foundation
{
  struct IAsyncAction;
  template <typename Progress>
  struct IAsyncActionWithProgress;
  template <typename Result>
  struct IAsyncOperation;
  template <typename Result, typename Progress>
  struct IAsyncOperationWithProgress;
}

IAsyncAction と IAsyncActionWithProgress 待機できる時に、非同期メソッドが完了するが、これらのインターフェイスの監視可能な結果を提供または戻り値を直接しない時期を決定します。IAsyncOperation と IAsyncOperationWithProgress、一方で、非同期メソッドが正常に完了したときに予想される結果の型を示すために、結果の型パラメーターを期待します。最後に、IAsyncActionWithProgress と IAsyncOperationWithProgress は、予想される定期的に実行時間の長い操作の非同期メソッドが完了するまで、進行状況に関する情報の種類を示す進行状況の型パラメーターを想定します。

非同期のメソッドの結果に待機するいくつかの方法はあります。これとここですべてに非常に長い記事にそれらを説明しません。さまざまな非同期の完了を処理する方法はありますがだけ 2 つあることをお勧めする: ブロックしている待機を実行する async.get メソッド、および co_await の非同期式、コルーチンのコンテキストでの協調的待機を実行します。どちらもお勧め他方より単純に異なる目的に使用するとします。ブロックの待機を行う方法をここで見てみましょう。

説明したように、次のように、get メソッドを使用してブロックの待機を実現できます。

IAsyncAction async = Sample::CopyAsync();
async.get();

非同期オブジェクトを保持してで任意の値が頻度の低いと、次の形式が推奨ため。

Sample::CopyAsync().get();

非同期のメソッドが完了するまでに、get メソッドが呼び出し元のスレッドをブロックことに注意してくださいに重要です。そのため、アプリが応答しなくなる可能性がありますので、UI スレッドで、get メソッドを使用する適切ではありません。アサーションは、これを行うにしようとすると、最適化されていないビルドで起動されます。Get メソッドは、コンソール アプリまたはここで、なんらかの理由により、する可能性がありますしないコルーチンを使用するバック グラウンド スレッドに最適です。

非同期のメソッドが完了すると、get メソッドは、呼び出し元に直接の結果を返します。IAsyncAction と IAsyncActionWithProgress の場合は、戻り値の型は void です。ファイルのコピー操作を開始する非同期メソッドに役立つ可能性があるけれども、小さいための非同期メソッドのようなものを読み取るファイルの内容例には、別の非同期メソッドを追加してみましょう。

struct Sample
{
  Sample() = delete;
  static Windows::Foundation::IAsyncAction CopyAsync();
  static Windows::Foundation::IAsyncOperation<hstring> ReadAsync();
};

ReadAsync の場合、get メソッドが正しく転送 hstring 結果、呼び出し元に、操作の完了後。

Sample::CopyAsync().get();
hstring result = Sample::ReadAsync().get();

どのような値が、正常に完了したときに非同期のメソッドによって返される実行が、get メソッドから返すと仮定した場合、結果の文字列が格納されます。実行返さない場合があります、たとえば、エラーが発生した場合。

Get メソッドは、UI スレッドからは使用できないことはこれを悪用、マシンの同時実行の完全な非同期メソッドが完了するまで、呼び出し元のスレッド人質が保持されるため、意味で制限されます。コルーチンを使用すると、不確定な一定の時間の働くような貴重なリソースを保持することがなく完了する非同期メソッドです。

非同期の完了を処理

完成したハンドルの非同期インターフェイスに一般に、もう少し詳しくでの動作にドリル ダウンみましょう。満足できない場合、get メソッドによって提供されるブロックしている待機と仮定した場合、どのようなその他のオプションはありますか。すぐに切り替えギアと完全にコルーチンのフォーカスが、時点で、詳しく見てみましょうこれらの非同期インターフェイスを提供しています。以前であった、get メソッドだけでなく、コルーチンのサポートは、これらのインターフェイスが含まれるコントラクトとステート マシンに依存します。私はありませんが多すぎるので詳しく本当にする必要はありませんについて詳しく理解が、これらが、そのため、少なくとも、使い慣れたであります基本見てみましょうについて詳しく説明して、それをさらに、通常活用のものの直接にある場合。

非同期インターフェイスの 4 つすべては、論理的に IAsyncInfo インターフェイスから派生します。IAsyncInfo で行うことができますであり、犯したも存在するオーバーヘッドのビットが追加されるため、ほとんどがあります。のみ IAsyncInfo メンバー本当に考慮する必要がありますが分かるようにする非同期メソッドが完了したかどうか、[状態] および [キャンセル] の結果が不要になった時間のかかる操作のキャンセルを要求するために使用できます。この設計は、本当に通常の非同期パターンと同様にしてだけ思ったこと完璧なので非常に近いものがあるためためすれば nitpick です。

ステータス メンバーは、実際に待つことがなく、非同期のメソッドが完了したかどうかを決定する必要がある場合に役立ちます。以下に例を示します。

auto async = ReadAsync();
if (async.Status() == AsyncStatus::Completed)
{
  auto result = async.GetResults();
  printf("%ls\n", result.c_str());
}

4 つの非同期インターフェイスの自体、IAsyncInfo いないのは、非同期のメソッドが完了したことを決定した後にのみ呼び出す必要があります GetResults メソッドの個別のバージョンを提供します。C + によって提供される、get メソッドと混同しない + WinRT です。Get が C + によって実装される GetResults は async メソッド自体によって実装される、+/WinRT です。GetResults しないブロック非同期のメソッドは実行されている途中で呼び出された場合 hresult_illegal_method_call 例外がスローされます可能性があります。ブロックの get メソッドを実装する方法を想像してくださいを開始することができます、あなたが疑いなく、します。概念的には、次のようになります。

auto get() const
{
  if (Status() != AsyncStatus::Completed)
  {
    // Wait for completion somehow ...
  }
  return GetResults();
}

実際の実装はもう少し複雑ですが、その gist をキャプチャします。ここでのポイントは、値を返す、IAsyncOperation か IAsyncAction で、しないかどうかにかかわらず GetResults が呼び出されることです。その理由は、GetResults が発生したエラーを async メソッドの実装内で発生し、必要に応じて、例外を再スローされますを反映するを担当することです。

残っている質問は、呼び出し元がの完了を待機できる方法です。新機能が含まれているを表示する非メンバーの get 関数を記述するつもりです。前の概念の get メソッドに触発この基本的なアウトラインから始めます。

template <typename T>
auto get(T const& async)
{
  if (async.Status() != AsyncStatus::Completed)
  {
    // Wait for completion somehow ...
  }
  return async.GetResults();
}

この関数テンプレート一方的に return ステートメントを使用するように 4 つの非同期インターフェイスを使用する必要があります。Genericity の C++ 言語で特別なプロビジョニングが行われ、もすることができます。

コールバックを登録するために使用する一意の完了メンバーは、次の 4 つの非同期インターフェイスの各: デリゲートと呼ばれる: 非同期メソッドが完了したときに呼び出されることです。通常は、C + + WinRT は、デリゲートを自動的に作成されます。必要なことを行うにはいくつかの関数のようなハンドラーを提供し、ラムダは、通常、最も簡単です。

async.Completed([](auto&& async, AsyncStatus status)
{
  // It's done!
});

デリゲートの最初のパラメーターの型は、非同期の完了したインターフェイスしますが、完了する必要があります単純シグナルと見なすことに注意してくださいになります。つまり、しない分量で一連の完了ハンドラー内のコード。基本的には、する必要があります記事として noexcept ハンドラー async メソッド自体を認識せずこのハンドラー内で発生している任意のエラーに対して何を行うため。それでは、どうしたらよいでしょうか。

イベントを使用して待機中のスレッドを単に通知できます。図 3 get 関数の外観を示しています。

イベントを使用してスレッドの待機中に通知する図 3

template <typename T>
auto get(T const& async)
{
  if (async.Status() != AsyncStatus::Completed)
  {
    handle signal = CreateEvent(nullptr, true, false, nullptr);
    async.Completed([&](auto&&, auto&&)
    {
      SetEvent(signal.get());
    });
    WaitForSingleObject(signal.get(), INFINITE);
  }
  return async.GetResults();
}

C + + WinRT の変数を取得メソッドを使用して、条件、スリム リーダー/ライター ロックに若干効率的になっているためです。このようなバリアント型のようになりますで表示される内容図 4です。

図 4 のスリム リーダー/ライター ロック状態変数を使用します。

template <typename T>
auto get(T const& async)
{
  if (async.Status() != AsyncStatus::Completed)
  {
    slim_mutex m;
    slim_condition_variable cv;
    bool completed = false;
    async.Completed([&](auto&&, auto&&)
    {
      {
        slim_lock_guard const guard(m);
        completed = true;
      }
      cv.notify_one();
    });
    slim_lock_guard guard(m);
    cv.wait(m, [&] { return completed; });
  }
  return async.GetResults();
}

たい場合、もちろん、C++ 標準ライブラリのミュー テックスと条件の変数を使用することができます。ここでのポイントは単に非同期の完了を配線フック関数は、完了ハンドラーが、非常に一般的に実行できます。

当然ながら、必要はありません、独自に記述するための get 関数、および複数の可能性の高いコルーチンなります非常に簡単かつ柔軟性の高い一般にします。それでも、これにより、一部の機能と、Windows ランタイムで柔軟性を評価する立てばさいわいです。

非同期オブジェクトを生成します。

非同期インターフェイスをについて説明してきましたおといくつか完了しくみ一般に、みましょうに注目を作成する、またはこれら 4 つの非同期インターフェイスの実装を作成します。既に学習したようには、C + WinRT インターフェイスを実装する + WinRT は非常に単純です。たとえば、実装 IAsyncAction のように図 5です。

図 5 IAsyncAction を実装します。

struct MyAsync : implements<MyAsync, IAsyncAction, IAsyncInfo>
{
  // IAsyncInfo members ...
  uint32_t Id() const;
  AsyncStatus Status() const;
  HRESULT ErrorCode() const;
  void Cancel() const;
  void Close() const;
  // IAsyncAction members ...
  void Completed(AsyncActionCompletedHandler const& handler) const;
  AsyncActionCompletedHandler Completed() const;
  void GetResults() const;
};

難易度は、これらのメソッドを実装する方法を検討するときに得られます。何らかの実装は想像することはできません、にないこのを正しく行う最初のリバース エンジニア リングせず方法既存言語プロジェクションを実際に実装することもほぼ不可能です。を参照してください、WinRT の非同期パターンのみ機能非常に特定の状態のコンピューターを使用してこれらのインターフェイスを実装するすべての場合とまったく同じようにします。各言語プロジェクションはこのステート マシンの実装方法について同じ前提を行い、不正な処理が行われますは少し異なる方法で実装した場合は、します。

これについて心配する必要はありませんさいわい、C + を除き、各言語プロジェクション + CX、既にこの正しくを実装します。C + 感謝 IAsyncAction の完全な実装をここでは +/WinRT のコルーチンのサポート。

IAsyncAction CopyAsync()
{
  co_return;
}

これで、これは、特に興味深い実装が、非常に教育と C + をどの程度の好例 + WinRT を実行します。これは、完全な実装であるため、これまでに学習した内容の一部を実行するために、使用できます。以前の CopyAsync 関数は、コルーチンです。IAsyncAction と IAsyncInfo の両方の実装をパノラマ コルーチンの戻り値の型を使用し、C++ コンパイラをもたらし、適切なタイミングでします。後で、それらの詳細情報の一部を紹介しましたが、今のところみましょう観察このコルーチンのしくみします。次のコンソール アプリを考慮してください。

IAsyncAction CopyAsync()
{
  co_return;
}
int main()
{
  IAsyncAction async = CopyAsync();
  async.get();
}

Main 関数では、返す IasyncAction CopyAsync 関数を呼び出します。しばらく CopyAsync 関数本体を忘れた場合、または定義次のように、単 IAsyncAction オブジェクトを返す関数であることが明らかになります。したがって、既に学習したすべての方法で使用することができます。

(この並べ替え) のコルーチンは co_return ステートメントまたは co_await ステートメントが必要です。もちろんがこのような複数のステートメントでも、コルーチンを実際にするためにこれらの少なくとも 1 つが必要です。ご想像 co_return ステートメントしない中断または非同期処理の任意の種類を紹介します。そのため、この CopyAsync 関数は、直ちに、または同期的に完了した IAsyncAction を生成します。とおり示しますすればことができます。

IAsyncAction Async()
{
  co_return;
}
int main()
{
  IAsyncAction async = Async();
  assert(async.Status() == AsyncStatus::Completed);
}

アサーションは、true であることが保証します。競合をここではありません。CopyAsync は単なる関数であるために、返し、返すには、最初のチャンスたまたま co_return ステートメントまでに、呼び出し元がブロックされます。これが意味を実装する必要があるいくつかの非同期コントラクトがある場合は、実装しない実際には任意の非同期処理を導入する必要があります、およびせずに直接ブロックまたはコンテキスト スイッチの概要、値を単に返すことです。ようにをダウンロードしてキャッシュされた値を取得する関数について考えて図 6です。

図 6 関数をダウンロードしてキャッシュされた値を返す

hstring m_cache;
IAsyncOperation<hstring> ReadAsync()
{
  if (m_cache.empty())
  {
    // Download and cache value ...
  }
  co_return m_cache;
}
int main()
{
  hstring message = ReadAsync().get();
  printf("%ls\n", message.c_str());
}

Readasync メソッドが呼び出されると、最初の時間では、キャッシュが空の可能性の高いされ、結果がダウンロードされます。おそらくこれは中断自体コルーチンこれの実行中です。中断は、実行を呼び出し元に返すことを意味します。呼び出し元には、非同期オブジェクトが、実際には、完了していない、したがって何らかの理由での完了を待機する必要が渡されます。

コルーチンの利点は、単一の抽象化 async オブジェクトを生成するためと同じ非同期オブジェクトを使用するための両方があることです。API または前述のよう、コンポーネントの作成者が非同期のメソッドを実装する可能性がありますが、API のコンシューマーまたはアプリの開発者も使用コルーチンを呼び出して、その完了を待機します。今すぐ書き直しますからメイン関数図 6コルーチンを待機中のために使用します。

IAsyncAction MainAsync()
{
  hstring result = co_await ReadAsync();
  printf("%ls\n", result.c_str());
}
int main()
{
  MainAsync().get();
}

基本的に古いメイン関数の本体の実行し、MainAsync コルーチンに移動います。Main 関数では、get メソッドを使用して、コルーチンの非同期的に実行中に終了してから、アプリケーションを禁止します。MainAsync 関数新しいものを持ち、co_await ステートメントです。ReadAsync が完了するまで、呼び出し元のスレッドをブロックする get メソッドを使用するのではなく、co_await ステートメントを使用して、ReadAsync 関数が協調的または非ブロッキング的に完了するまで待機します。これは中断ポイントで意図したものです。Co_await ステートメントでは、中断ポイントを表します。このアプリで ReadAsync を 1 回呼び出しますのみより有益なアプリで複数回を呼び出されていることを想定できます。初めて呼び出される MainAsync コルーチンが実際に中断され、呼び出し元に制御を戻します。呼び出されると、2 回目にしないすべての中断しますが、ではなく、値を直接返します。

コルーチンは非常に多くの C++ 開発者に気に不適切なは、このままと思われる場合ではなく魔法ようにします。これらの概念は作成してコルーチンを自分でデバッグを開始すると、非常に明確になります。良いニュースは、既に把握している非同期 Windows で提供されている Api を使用するコルーチンの効果的に使用するには十分です。たとえば、ことができますについて理由にでコンソール アプリ図 7動作します。

図 7 コルーチンでのサンプル コンソール アプリ

#include "winrt/Windows.Web.Syndication.h"
using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Web::Syndication;
IAsyncAction MainAsync()
{
  Uri uri(L"https://kennykerr.ca/feed");
  SyndicationClient client;
  SyndicationFeed feed = co_await client.RetrieveFeedAsync(uri);
  for (auto&& item : feed.Items())
  {
    hstring title = item.Title().Text();
    printf("%ls\n", title.c_str());
  }
}
int main()
{
  init_apartment();
  MainAsync().get();
}

試してみます今すぐと Windows 上の最新の C++ を使用するがどの程度楽しいだけを参照してください。

コルーチンおよびスレッド プール

基本的なコルーチンの作成は簡単です。非常に簡単に co_await 他の非同期アクションまたは操作、co_return 値、単にしたり、2 つの組み合わせを作成できます。すべての非同期ではないコルーチンを次に示します。

IAsyncOperation<int> return_123()
{
  co_return 123;
}

同期的に、実行場合でもまだ、IAsyncOperation インターフェイスの完全に有効な実装を生成します。

int main()
{
  int result = return_123().get();
  assert(result == 123);
}

次に、値を返す前に 5 秒間待機するいずれかを示します。

using namespace std::chrono;
IAsyncOperation<int> return_123_after_5s()
{
  co_await 5s;
  co_return 123;
}

次のいずれかが非同期的に実行してから表面上とまだ、main 関数オブジェクトはほとんど変更されず、get 関数のブロックの動作のおかげ。

int main()
{
  int result = return_123_after_5s().get();
  assert(result == 123);
}

最後のコルーチンで co_return ステートメントは、co_await 式は chrono 期間スレッド プール タイマーを使用するために Windows スレッド プールで実行されます。Co_await ステートメントは中断ポイントを表し、コルーチンには完全に別のスレッドの中断の後に再開可能性があることが明らかにする必要があります。この明示的な resume_background を使用して作成することも。

IAsyncOperation<int> background_123()
{
  co_await resume_background();
  co_return 123;
}

明確な遅延今回はありません、コルーチンはスレッド プールで再開することが保証されます。What-if かわからないですか。だけを潜在的な記憶域から値を取得する必要がある場合は、コンテキストの切り替えを導入してキャッシュされた値を持つ可能性があります。これは、ここではコルーチンもあること、関数、通常すべての規則を適用するように注意してください。

IAsyncOperation<int> background_123()
{
  static std::atomic<int> result{0};
  if (result == 0)
  {
    co_await resume_background();
    result = 123;
  }
  co_return result;
}

これがのみ条件付きでしようとする同時実行を紹介します。複数のスレッドでしたあるいはで競合 background_123 を呼び出すと、うちのいくつかの原因と分割不可能な変数の先読みをスレッド プールが最終的に再開して、コルーチンの同期をとって実行が開始されます。つまり、最悪のケースではもちろん、します。

値準備ができていることを示す、シグナルが発生したら値をストレージから読み取るだけあるとしましょう。2 つのコルーチンで使用できる図 8このです。

図 8 のシグナルが発生した後、記憶域から値を読み取る

handle m_signal{ CreateEvent(nullptr, true, false, nullptr) };
std::atomic<int> m_value{ 0 };
IAsyncAction prepare_result()
{
  co_await 5s;
  m_value = 123;
  SetEvent(m_signal.get());
}
IAsyncOperation<int> return_on_signal()
{
  co_await resume_on_signal(m_signal.get());
  co_return m_value;
}

最初のコルーチン人為的に 5 秒間待機するには、値を設定および Win32 イベントに通知します。2 番目のコルーチンがシグナル状態になる、イベントを待機し、単に値を返します。もう一度、スレッド プールを使用して、先頭の効率的でスケーラブルな実装に、イベントを待機します。2 つのコルーチンを調整することは簡単です。

int main()
{
  prepare_result();
  int result = return_on_signal().get();
  assert(result == 123);
}

Main 関数は最初のコルーチンが開始されるとが、その完了を待機をブロックしません。2 番目のコルーチンでは、その実行をブロックして、値の待機が直ちに開始します。

コルーチンと呼び出し元コンテキスト

これまでは、スレッド プールで説明してきましたか、どのようなバック グラウンド スレッドが呼び出す可能性があります。C + + WinRT に Windows スレッド プールが大好きなもの可変する必要しますが、ありますいないいくつかのユーザーの操作を表すフォア グラウンド スレッドに戻さ作業を取得します。実行コンテキストを正確に制御を実行する方法を見てみましょう。

コルーチンは、同時実行の導入またはその他の Api での待機時間で処理に使用できる、ためを任意の時点の指定されたコルーチンの実行コンテキストについてに混乱が発生します。いくつかをオフにしてみましょう。

図 9呼び出し元のスレッドに関する基本的な情報を印刷する単純な関数を示しています。

図 9 の呼び出し元スレッドに関する基本的な情報を出力します

void print_context()
{
  printf("thread:%d apartment:", GetCurrentThreadId());
  APTTYPE type;
  APTTYPEQUALIFIER qualifier;
  HRESULT const result = CoGetApartmentType(&type, &qualifier);
  if (result == S_OK)
  {
    puts(type == APTTYPE_MTA ? "MTA" : "STA");
  }
  else
  {
    puts("N/A");
  }
}

これではありませんアパートメントの詳細については、の排他的なまたは安全な方法でダンプですが、今回は十分今日です。COM 見えますが、N/A「該当しない」、その他の名前ではないことを考えるかもしれません。2 つのプライマリ アパートメント モデルがあることに注意してください。プロセスには最大で 1 つマルチ スレッド アパートメント (MTA) があり、シングル スレッド アパートメント (STA) の任意の数を持っている可能性があります。アパートメントは、COM のリモート処理のアーキテクチャに対応するが、スレッド セーフであることの COM オブジェクトをサポートするために使用されてきましたするように設計が現実的な問題です。

STA は、オブジェクトが作成されるスレッドからのみ呼び出されるようにする COM を使用します。もちろん、これは、アパートメント スレッドに戻るには、他のスレッドからの呼び出しをマーシャ リングするいくつかのメカニズムを意味します。Sta は通常、このメッセージのループやディスパッチャーのキューを使用します。MTA は、スレッドの関係が存在しないが、その他のいくつかのアパートメントでの呼び出しを生成する場合に必要なそのマーシャ リングはことを示すために COM を使用します。オブジェクトとのスレッド間の関係は複雑で、別の日を保存します。シナリオとしては理想的とは、オブジェクトがアジャイルしたがってアパートメント アフィニティから無料であることを告げる場合です。

みましょう print_context 関数を使用して、書き込みをいくつか興味深いプログラムです。ここでを呼び出す print_context resume_background を使用して (いるスレッド プール) をバック グラウンド スレッドに作業を移動する前後に。

IAsyncAction Async()
{
  print_context();
  co_await resume_background();
  print_context();
}

次の呼び出し元を考慮してください。

int main()
{
  init_apartment();
  Async().get();
}

呼び出し元が、コルーチン (または任意の関数) に対して元または呼び出し元のコンテキストを決定するので重要です。ここでは、init_apartment は引数を指定せずに main から呼び出されます。これは、アプリのプライマリ スレッドには、MTA を参加させることを意味します。したがって、メッセージ ループまたは任意の種類のディスパッチャーする必要がないです。また、スレッドがコルーチンを完了するまで待機するブロックの get 機能を使用して、ここで示したようの実行をブロック問題なくことを意味します。呼び出し元のスレッドは MTA スレッドであるため、コルーチンは MTA で実行を開始します。Resume_background co_await 式は、コルーチンを一時的に中断するために使用できるように、呼び出し元に呼び出し元のスレッドが解放され、自体コルーチンは無料で、スレッドはスレッド プールから提供されるため、コルーチンはすぐに再開するには同時に実行を続けます。1 回 (それ以外の場合は、バック グラウンド スレッドと呼ばれます)、スレッド プールで print_context、もう一度呼び出されます。表示されるこのプログラムを実行する場合は次に示します。

thread:18924 apartment:MTA
thread:9568 apartment:MTA

スレッド識別子は関係ありません。重要なは一意であることです。スレッド プール内に一時的に提供されたスレッドは MTA スレッドでもに注意してください。かどうか、そのスレッドで init_apartment お電話させていないこのする方法Print_context 関数にステップすることがわかります、APTTYPEQUALIFIER がこれらのスレッドを区別して、暗黙の型としてのスレッド関連を識別します。安全にすれば、スレッド プールのスレッドが実際には、MTA スレッドであるプロセスが履歴に保持する、MTA 他のいくつかの方法で提供されます。

Resume_background co_await 式が、どのようなスレッドまたはそれがもともとが実行されているアパートメントに関係なく、スレッド プールにコルーチンの実行コンテキストを切り替える効果的にことが重要です。コルーチンが必要がありますいない呼び出し元のブロックし、コルーチンの評価がいくつか計算バウンドの操作、可能性のある呼び出し元のスレッドをブロックする前に中断されていることを確認を推測する必要があります。その resume_background は常に使用することを意味します。コルーチンはコルーチンの他のいくつかのセットを集計するには、純粋な存在可能性があります。その場合は、コルーチン内の任意の co_await 式は、呼び出し元のスレッドがブロックされていないことを確認するために必要な中断を提供する可能性があります。

呼び出し元を変更する場合、アプリのメインの動作について検討関数は、次のようにします。

int main()
{
  init_apartment(apartment_type::single_threaded);
  Async();
  MSG message;
  while (GetMessage(&message, nullptr, 0, 0))
  {
    DispatchMessage(&message);
  }
}

これは、十分に妥当なようです。プライマリ スレッドでは、(ブロックすることがなく任意の方法で)、非同期関数を呼び出してと、STA でアパートメント間の呼び出しを処理できることを確認するメッセージ ループに入る STA スレッドになります。問題は、MTA が作成されたこと、スレッド プールが所有するスレッドが、MTA を暗黙的に参加しませんし、COM の使用したがってことはできません、サービスのアクティブ化などです。表示されるプログラムを今すぐ実行する場合は次に示します。

thread:17552 apartment:STA
thread:19300 apartment:N/A

Resume_background 中断の後に注意してください。、コルーチンには、COM ランタイムに依存するコードを実行するためのアパートメント コンテキストがないです。本当が必要な場合、STA プライマリ スレッドで、この問題を簡単に解決いることを確認、MTA"に常時"に関係なくのように図 10です。

「常時オン」は、MTA を確保する図 10 です。

int main()
{
  CO_MTA_USAGE_COOKIE mta{};
  check_hresult(CoIncrementMTAUsage(&mta));
  init_apartment(apartment_type::single_threaded);
  Async();
  MSG message;
  while (GetMessage(&message, nullptr, 0, 0))
  {
    DispatchMessage(&message);
  }
}

CoIncrementMTAUsage 関数は、MTA が作成されたことを確認します。呼び出し元のスレッドはまで、または同様に、ここで、その他のいくつかのアパートメントに参加する明示的な選択が行われた場合を除き、MTA の暗黙的なメンバーになります。プログラムを再実行した場合、目的の結果を取得しました。

thread:11276 apartment:STA
thread:9412 apartment:MTA

これは、本質的に環境内で、最近のほとんどの Windows アプリが、相手を見つけるもわかりますもう少し制御または、自分の環境を作成する方法についてです。コルーチンの調査に伴い、自分に次回を結合します。

まとめ

Async および C++ ではコルーチンで非常に深い出てしました。常に同時実行に関する詳細。このような素晴らしいトピックは、この概要は取得する C + の使用を開始するときに、C++ アプリとコンポーネント、すぐに膨大な電源で非同期に対処することが簡単な方法を膨らませていました立てばさいわい + WinRT です。


Kenny Kerr作成者は、システム プログラマおよび C + の作成者は、+/WinRT です。Windows チーム Microsoft 驚異的な簡単に美しい高性能なアプリやコンポーネントを作成する開発者を有効にすると、C++ の Windows の将来が彼に署名されるとは逆のエンジニアもできます。