Important
このトピックでは 、コルーチン と co_awaitの概念について説明します。これは、UI と UI 以外のアプリケーションの両方で使用することをお勧めします。 わかりやすくするために、この入門トピックのコード例のほとんどは、Windowsコンソール アプリケーション (C++/WinRT) プロジェクトを示しています。 このトピックの後半のコード例ではコルーチンを使用していますが、便宜上、コンソール アプリケーションの例でも、終了直前にブロッキング get 関数呼び出しを引き続き使用し、出力の表示が完了する前にアプリケーションが終了しないようにしています。 UI スレッドからそのようなこと、つまりブロッキングする get 関数の呼び出しは行いません。 代わりに、 co_await ステートメントを使用します。 UI アプリケーションで使用する手法については、「 高度なコンカレンシーと非同期」のトピックで説明されています。
この入門トピックでは、C++/WinRT を使用して非同期オブジェクトWindows ランタイム作成および使用する方法をいくつか示します。 このトピックを読んだ後、特に UI アプリケーションで使用する手法については、「 高度なコンカレンシーと非同期性」も参照してください。
非同期操作と Windows ランタイム の "Async" 関数
完了するまでに 50 ミリ秒以上かかる可能性があるWindows ランタイム API は、非同期関数として実装されます (名前は "Async" で終わる)。 非同期関数の実装は、別のスレッドで作業を開始し、非同期操作を表すオブジェクトを使用して直ちに返します。 非同期操作が完了すると、返されたオブジェクトには、作業の結果として得られた値が含まれます。 Windows::Foundation Windows ランタイム名前空間には、4 種類の非同期操作オブジェクトが含まれています。
- IAsyncAction,
- IAsyncActionWithProgress<TProgress>,
- IAsyncOperation<TResult>、および
- IAsyncOperationWithProgress<TResult、TProgress>。
これらの非同期操作の各型は、winrt::Windows::Foundation C++/WinRT 名前空間内の対応する型に投影されます。 C++/WinRT には、内部 await アダプター構造体も含まれています。 直接使用することはありませんが、その構造体のおかげで、 co_await ステートメントを記述して、これらの非同期操作の種類のいずれかを返す関数の結果を協調的に待機することができます。 また、これらの型を返す独自のコルーチンを作成することもできます。
非同期Windows関数の例として、SyndicationClient::RetrieveFeedAsync があります。これは、IAsyncOperationWithProgress<TResult、TProgress> 型の非同期操作オブジェクトを返します。
C++/WinRT を使用してそのような API を呼び出す方法 (最初にブロックしてから非ブロッキング) を見てみましょう。 基本的なアイデアを説明するためだけに、次のいくつかのコード例で Windows コンソール アプリケーション (C++/WinRT) プロジェクトを使用します。 UI アプリケーションに適した手法については、「 高度なコンカレンシーと非同期」を参照してください。
呼び出し元スレッドをブロックする
次のコード例では、 RetrieveFeedAsync から非同期操作オブジェクトを受け取り、そのオブジェクトを 呼 び出して、非同期操作の結果が使用可能になるまで呼び出し元のスレッドをブロックします。
この例をコピーして、Windows コンソール アプリケーション (C++/WinRT) プロジェクトのメイン ソース コード ファイルに直接貼り付ける場合は、最初にプロジェクトのプロパティでプリコンパイル済みヘッダーを使用しないを設定します。
// main.cpp
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Web.Syndication.h>
using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Web::Syndication;
void ProcessFeed()
{
Uri rssFeedUri{ L"https://blogs.windows.com/feed" };
SyndicationClient syndicationClient;
SyndicationFeed syndicationFeed{ syndicationClient.RetrieveFeedAsync(rssFeedUri).get() };
// use syndicationFeed.
}
int main()
{
winrt::init_apartment();
ProcessFeed();
}
get を呼び出すとコーディングが便利になり、何らかの理由でコルーチンを使用したくないコンソール アプリやバックグラウンド スレッドに最適です。 ただし、並行実行でも非同期でもないため、UI スレッドには適していません(また、これを UI スレッドで使用しようとすると、非最適化ビルドではアサーションが発生します)。 OS スレッドが他の便利な作業を行わないようにするには、別の手法が必要です。
コルーチンを記述する
C++/WinRT は、C++ コルーチンをプログラミング モデルに統合して、結果を協調的に待機する自然な方法を提供します。 コルーチンを記述することで、独自のWindows ランタイム非同期操作を生成できます。 次のコード例では、 ProcessFeedAsync がコルーチンです。
Note
get 関数は C++/WinRT プロジェクション型 winrt::Windows::Foundation::IAsyncAction に存在するため、任意の C++/WinRT プロジェクト内から関数を呼び出すことができます。 get は実際の Windows ランタイム 型 IAsyncAction のアプリケーション バイナリ インターフェイス (ABI) サーフェスの一部ではないため、IAsyncAction インターフェイスのメンバーとしてリストされている関数が見つかりません。
// main.cpp
#include <iostream>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Web.Syndication.h>
using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Web::Syndication;
void PrintFeed(SyndicationFeed const& syndicationFeed)
{
for (SyndicationItem const& syndicationItem : syndicationFeed.Items())
{
std::wcout << syndicationItem.Title().Text().c_str() << std::endl;
}
}
IAsyncAction ProcessFeedAsync()
{
Uri rssFeedUri{ L"https://blogs.windows.com/feed" };
SyndicationClient syndicationClient;
SyndicationFeed syndicationFeed{ co_await syndicationClient.RetrieveFeedAsync(rssFeedUri) };
PrintFeed(syndicationFeed);
}
int main()
{
winrt::init_apartment();
auto processOp{ ProcessFeedAsync() };
// do other work while the feed is being printed.
processOp.get(); // no more work to do; call get() so that we see the printout before the application exits.
}
コルーチンは、中断および再開できる関数です。 上記の ProcessFeedAsync コルーチンでは、 co_await ステートメントに達すると、コルーチンは RetrieveFeedAsync 呼び出しを非同期的に開始し、直ちに自身を中断し、呼び出し元に制御を戻します (上記の例では メイン です)。
main は、フィードの取得と印刷中に引き続き作業を行うことができます。 それが完了すると ( RetrieveFeedAsync 呼び出しが完了すると)、 ProcessFeedAsync コルーチンは次のステートメントで再開されます。
コルーチンを他のコルーチンに集約できます。 または、 get を呼び出してブロックし、完了するのを待ちます(ある場合は結果を取得します)。 または、Windows ランタイムをサポートする別のプログラミング言語に渡すことができます。
また、デリゲートを使用して、非同期アクションと操作の完了イベントや進行状況イベントを処理することもできます。 詳細とコード例については、「 非同期アクションと操作のデリゲート型」を参照してください。
ご覧のように、上記のコード例では、main を終了する直前に、ブロッキング get 関数呼び出しを引き続き使用します。 ただし、これは、出力の印刷を終了する前にアプリケーションが終了しないようにするためだけです。
Windows ランタイム型を非同期的に返す
次の例では、特定の URI に対して RetrieveFeedAsync の呼び出しをラップして、SyndicationFeed を非同期的に返す RetrieveBlogFeedAsync 関数を提供します。
// main.cpp
#include <iostream>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Web.Syndication.h>
using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Web::Syndication;
void PrintFeed(SyndicationFeed const& syndicationFeed)
{
for (SyndicationItem const& syndicationItem : syndicationFeed.Items())
{
std::wcout << syndicationItem.Title().Text().c_str() << std::endl;
}
}
IAsyncOperationWithProgress<SyndicationFeed, RetrievalProgress> RetrieveBlogFeedAsync()
{
Uri rssFeedUri{ L"https://blogs.windows.com/feed" };
SyndicationClient syndicationClient;
return syndicationClient.RetrieveFeedAsync(rssFeedUri);
}
int main()
{
winrt::init_apartment();
auto feedOp{ RetrieveBlogFeedAsync() };
// do other work.
PrintFeed(feedOp.get());
}
上の例では、 RetrieveBlogFeedAsync は、進行状況と戻り値の両方を持つ IAsyncOperationWithProgress を返します。 RetrieveBlogFeedAsync がその処理を行い、フィードを取得している間は、他の作業を行うことができます。 次に、その非同期操作オブジェクトの get を呼び出してブロックし、完了するまで待ってから、操作の結果を取得します。
Windows ランタイム型を非同期的に返す場合は、IAsyncOperation<TResult> または IAsyncOperationWithProgress<TResult、TProgress> を返す必要があります。 ファーストパーティまたはサードパーティのランタイム クラス、あるいは Windows ランタイム 関数に渡すことも Windows ランタイム 関数から受け取ることもできる任意の型(たとえば、int や winrt::hstring)であれば、該当します。 Windows ランタイム 型ではない型でこれらの非同期操作型のいずれかを使おうとすると、コンパイラは "T must be WinRT type" というエラーを出して知らせてくれます。
コルーチンに少なくとも 1 つの co_await ステートメントがない場合、コルーチンとして修飾するには、少なくとも 1 つの co_return または 1 つの co_yield ステートメントが必要です。 コルーチンが非同期性を導入せずに値を返すことができる場合があります。したがって、コンテキストをブロックしたり切り替えたりすることはありません。 値をキャッシュすることでそれを実現する例を以下に示します(2回目以降の呼び出しでは)。
winrt::hstring m_cache;
IAsyncOperation<winrt::hstring> ReadAsync()
{
if (m_cache.empty())
{
// Asynchronously download and cache the string.
}
co_return m_cache;
}
Windows-Runtime 以外の型を非同期的に返す
Windows ランタイム 型ではない型を非同期で返す場合は、並列パターン ライブラリ (PPL) concurrency::task を返す必要があります。 std::future よりも優れたパフォーマンス (および今後の互換性) が得られるので、コンカレンシー::task をお勧めします。
Tip
<pplawait.h>を含める場合は、コルーチンの種類としてコンカレンシー::task を使用できます。
// main.cpp
#include <iostream>
#include <ppltasks.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Web.Syndication.h>
using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Web::Syndication;
concurrency::task<std::wstring> RetrieveFirstTitleAsync()
{
return concurrency::create_task([]
{
Uri rssFeedUri{ L"https://blogs.windows.com/feed" };
SyndicationClient syndicationClient;
SyndicationFeed syndicationFeed{ syndicationClient.RetrieveFeedAsync(rssFeedUri).get() };
return std::wstring{ syndicationFeed.Items().GetAt(0).Title().Text() };
});
}
int main()
{
winrt::init_apartment();
auto firstTitleOp{ RetrieveFirstTitleAsync() };
// Do other work here.
std::wcout << firstTitleOp.get() << std::endl;
}
パラメーターの受け渡し
同期関数の場合は、既定で const& パラメーターを使用する必要があります。 これにより、コピーに伴うオーバーヘッドを避けられます(これには参照カウント処理が伴い、そのためインターロックされたインクリメントとデクリメントが必要になります)。
// Synchronous function.
void DoWork(Param const& value);
ただし、コルーチンに参照パラメーターを渡すと、問題が発生する可能性があります。
// NOT the recommended way to pass a value to a coroutine!
IASyncAction DoWorkAsync(Param const& value)
{
// While it's ok to access value here...
co_await DoOtherWorkAsync(); // (this is the first suspension point)...
// ...accessing value here carries no guarantees of safety.
}
コルーチンでは、最初の中断ポイントで制御が呼び出し元に戻り、呼び出し側のフレームがスコープ外になるまで、実行は同期的に行われます。 コルーチンが再開されるまでに、参照パラメーターが参照するソース値に何か起こった可能性があります。 コルーチンの観点からは、参照パラメーターの有効期間は制御できません。 したがって、上記の例では、までco_awaitにアクセスしても安全ですが、その後にはアクセスできません。 呼び出し元によって その値 が破棄された場合、その後コルーチン内で値にアクセスしようとすると、メモリが破損します。 また、その関数が中断し、再開後に 値 を使用しようとするリスクがある場合、 DoOtherWorkAsync に 値 を安全に渡すこともできません。
中断および再開の後でもパラメーターを安全に使用できるようにするには、コルーチンでは既定で値渡しを使用して値キャプチャされるようにし、ライフタイムの問題を回避する必要があります。 安全であると確信しているため、そのガイダンスから逸脱する可能性がある場合は、まれになります。
// Coroutine
IASyncAction DoWorkAsync(Param value); // not const&
値渡しを行うには、引数を移動またはコピーするコストが低い必要があります。これは通常、スマート ポインターの場合です。
また、const 値を渡す (値を移動する場合を除く) の方が良い方法であるとも言える。 コピー元のソース値には影響しませんが、意図が明確になり、誤ってコピーを変更した場合に役立ちます。
// coroutine with strictly unnecessary const (but arguably good practice).
IASyncAction DoWorkAsync(Param const value);
標準ベクトルを非同期呼び出し先に渡す方法を扱う、 標準配列とベクトルも参照してください。
コルーチンのシグネチャを変更できないが、実装を変更できる場合は、最初の co_awaitの前にローカル コピーを作成できます。
IASyncAction DoWorkAsync(Param const& value)
{
auto safe_value = value;
// It's ok to access both safe_value and value here.
co_await DoOtherWorkAsync();
// It's ok to access only safe_value here (not value).
}
Paramコピーにコストがかかる場合は、最初のco_awaitの前に必要な部分だけを抽出します。
IASyncAction DoWorkAsync(Param const& value)
{
auto safe_data = value.data;
// It's ok to access safe_data, value.data, and value here.
co_await DoOtherWorkAsync();
// It's ok to access only safe_data here (not value.data, nor value).
}
クラス メンバー コルーチンで この ポインターに安全にアクセスする
C++/WinRT の強参照と弱参照を参照してください。
重要な API
- concurrency::task クラス
- IAsyncAction インターフェイス
- IAsyncActionWithProgress<TProgress> インターフェイス
- IAsyncOperation<TResult> インターフェイス
- IAsyncOperationWithProgress<TResult,TProgress> インターフェイス
- SyndicationClient::RetrieveFeedAsync メソッド
- SyndicationFeed クラス
関連トピック
Windows developer