C++/WinRT を使用した同時実行操作と非同期操作Concurrency and asynchronous operations with C++/WinRT

重要

このトピックでは、"コルーチン" と co_await の概念について説明します。これは、UI "および" UI のないアプリケーションの両方で使用することをお勧めします。This topic introduces the concepts of coroutines and co_await, which we recommend that you use in both your UI and in your non-UI applications. わかりやすくするために、この入門トピックのコード例のほとんどは、Windows コンソール アプリケーション (C++/WinRT) プロジェクトを示しています。For simplicity, most of the code examples in this introductory topic show Windows Console Application (C++/WinRT) projects. このトピック内の後述のコード例でコルーチンを使用しますが、便宜上、コンソール アプリケーションの例では、終了する直前にブロッキング get 関数呼び出しも使用して、出力の印刷を完了する前にアプリケーションが終了しないようにしています。The later code examples in this topic do use coroutines, but for convenience the console application examples also continue to use the blocking get function call just before exiting, so that the application doesn't exit before finishing printing its output. それ (ブロッキング get 関数の呼び出し) は、UI スレッドからは行いません。You won't do that (call the blocking get function) from a UI thread. 代わりに、co_await ステートメントを使用します。Instead, you'll use the co_await statement. UI アプリケーションで使用する手法の詳細については、「より高度な同時実行操作と非同期操作」を参照してください。The techniques that you'll use in your UI applications are described in the topic More advanced concurrency and asynchrony.

この入門トピックでは、C++/WinRT を使用した Windows ランタイムの非同期オブジェクトを作成および利用する方法について少し説明します。This introductory topic shows some of the ways in which you can both create and consume Windows Runtime asynchronous objects with C++/WinRT. このトピックを読んだ後、特に UI アプリケーションで使用する方法については、「より高度な同時実行操作と非同期操作」も参照してください。After reading this topic, especially for techniques you'll use in your UI applications, also see More advanced concurrency and asynchrony.

非同期操作と Windows ランタイムの "非同期" 関数Asynchronous operations and Windows Runtime "Async" functions

完了までの時間が 50 ミリ秒を超える可能性がある Windows ランタイム API は、非同期の関数 (末尾が "Async") として実装されます。Any Windows Runtime API that has the potential to take more than 50 milliseconds to complete is implemented as an asynchronous function (with a name ending in "Async"). 非同期関数を実装すると、別のスレッドの作業が開始され、非同期操作を表すオブジェクトがすぐに返されます。The implementation of an asynchronous function initiates the work on another thread, and returns immediately with an object that represents the asynchronous operation. 非同期操作が完了すると、作業結果が含まれるオブジェクトが返されます。When the asynchronous operation completes, that returned object contains any value that resulted from the work. Windows::Foundation Windows ランタイムの名前空間には 4 種類の非同期操作オブジェクトが含まれます。The Windows::Foundation Windows Runtime namespace contains four types of asynchronous operation object.

各非同期操作は、winrt::Windows::Foundation C++/WinRT の名前空間で対応する型に投影されます。Each of these asynchronous operation types is projected into a corresponding type in the winrt::Windows::Foundation C++/WinRT namespace. また、C++/WinRT には内部 await アダプター構造体も含まれます。C++/WinRT also contains an internal await adapter struct. これを直接使用することはありませんが、この構造体により、これらの非同期操作型のいずれかを返す関数の結果を一緒に待機するように co_await ステートメントを記述することができます。You don't use it directly but, thanks to that struct, you can write a co_await statement to cooperatively await the result of any function that returns one of these asynchronous operation types. その後、これらの型を返す独自のコルーチンを作成できます。And you can author your own coroutines that return these types.

たとえば、非同期の Windows 関数である SyndicationClient::RetrieveFeedAsync は、IAsyncOperationWithProgress<TResult, TProgress> 型の非同期操作オブジェクトを返します。An example of an asynchronous Windows function is SyndicationClient::RetrieveFeedAsync, which returns an asynchronous operation object of type IAsyncOperationWithProgress<TResult, TProgress>.

それでは、C++/WinRT を使用してそのような API を呼び出すためのいくつかの方法 (最初はブロック、次に非ブロック) を見てみましょう。Let's look at some ways—first blocking, and then non-blocking—of using C++/WinRT to call an API such as that. 基本的なアイデアを示すために、次の複数のコード例では、Windows コンソールアプリケーション (C++/WinRT) プロジェクトを使用します。Just for illustration of the basic ideas, we'll be using a Windows Console Application (C++/WinRT) project in the next few code examples. UI アプリケーションにより適した手法については、「より高度な同時実行操作と非同期操作」を参照してください。Techniques that are more appropriate for a UI application are discussed in More advanced concurrency and asynchrony.

呼び出しスレッドのブロックBlock the calling thread

次のコード例では、RetrieveFeedAsync から非同期操作オブジェクトを受け取り、非同期操作の結果が利用可能になるまで呼び出しスレッドをブロックするようにこのオブジェクトで get を呼び出します。The code example below receives an asynchronous operation object from RetrieveFeedAsync, and it calls get on that object to block the calling thread until the results of the asynchronous operation are available.

この例を Windows コンソール アプリケーション (C++/WinRT) プロジェクトのメイン ソース コード ファイルに直接コピーして貼り付ける場合は、最初にプロジェクト プロパティで [プリコンパイル済みヘッダーを使用しない] を設定します。If you want to copy-paste this example directly into the main source code file of a Windows Console Application (C++/WinRT) project, then first set Not Using Precompiled Headers in project properties.

// 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 を呼び出すとコーディングが簡単になり、何らかの理由でコルーチンを使用しないコンソール アプリやバックグラウンド スレッドに最適です。Calling get makes for convenient coding, and it's ideal for console apps or background threads where you may not want to use a coroutine for whatever reason. ただし、同時実行でも非同期でもないため、UI スレッドには適していません (UI スレッドで使用しようとすると、最適化されないビルドでアサーションが発生します)。But it's not concurrent nor asynchronous, so it's not appropriate for a UI thread (and an assertion will fire in unoptimized builds if you attempt to use it on one). OS スレッドの他の有用な作業を妨げないように、さまざまな方法が必要です。To avoid holding up OS threads from doing other useful work, we need a different technique.

コルーチンの作成Write a coroutine

C++/WinRT は C++ コルーチンをプログラミング モデルに統合し、結果を連携して待機するための自然な方法を提供します。C++/WinRT integrates C++ coroutines into the programming model to provide a natural way to cooperatively wait for a result. コルーチンを作成すると、独自の Windows ランタイム非同期操作を作成することができます。You can produce your own Windows Runtime asynchronous operation by writing a coroutine. 次のコード例で、ProcessFeedAsync はコルーチンします。In the code example below, ProcessFeedAsync is the coroutine.

注意

get 関数は、C++/WinRT プロジェクション型 winrt::Windows::Foundation::IAsyncAction に存在するため、任意の C++/WinRT プロジェクト内からこの関数を呼び出すことができます。The get function exists on the C++/WinRT projection type winrt::Windows::Foundation::IAsyncAction, so you can call the function from within any C++/WinRT project. get 関数が IAsyncAction インターフェイスのメンバーとして一覧表示されないのは、この関数が実際の Windows ランタイム型 IAsyncAction のアプリケーション バイナリ インターフェイス (ABI) サーフェスの一部ではないからです。You will not find the function listed as a member of the IAsyncAction interface, because get is not part of the application binary interface (ABI) surface of the actual Windows Runtime type 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.
}

コルーチンは中断して再開できる関数です。A coroutine is a function that can be suspended and resumed. 上記の ProcessFeedAsync コルーチンでは、co_await ステートメントに到達すると、コルーチンは RetrieveFeedAsync 呼び出しを非同期的に起動してからすぐに自身を中断し、呼び出し元 (上記の例の main) に戻ります。In the ProcessFeedAsync coroutine above, when the co_await statement is reached, the coroutine asynchronously initiates the RetrieveFeedAsync call and then it immediately suspends itself and returns control back to the caller (which is main in the example above). 次に、フィードが取得および印刷されるまで、main は作業を続けます。main can then continue to do work while the feed is being retrieved and printed. 完了すると (RetrieveFeedAsync 呼び出しが完了すると)、ProcessFeedAsync コルーチンは次のステートメントを再開します。When that's done (when the RetrieveFeedAsync call completes), the ProcessFeedAsync coroutine resumes at the next statement.

コルーチンは別のコルーチンに集約することができます。You can aggregate a coroutine into other coroutines. または、get を呼び出してブロックするか、完了するまで待機します (結果が存在する場合には取得します)。Or you can call get to block and wait for it to complete (and get the result if there is one). または、Windows ランタイムをサポートする別のプログラミング言語に渡すことができます。Or you can pass it to another programming language that supports the Windows Runtime.

また、デリゲートを使用して、非同期アクションおよび操作の完了イベントと進行状況イベントを処理することもできます。It's also possible to handle the completed and/or progress events of asynchronous actions and operations by using delegates. 詳細とコード例については、「非同期アクションと操作のデリゲート型」をご覧ください。For details, and code examples, see Delegate types for asynchronous actions and operations.

見てわかるように、上記のコード例では、引き続き、main の終了直前にブロッキング get 関数呼び出しを使用しています。As you can see, in the code example above, we continue to use the blocking get function call just before exiting main. これは、出力の印刷が完了する前にアプリケーションが終了しないようにすることのみが目的です。But that's only so that the application doesn't exit before finishing printing its output.

Windows ランタイム型を非同期的に返すAsynchronously return a Windows Runtime type

次の例では、特定の URI で呼び出しを RetrieveFeedAsync にラップして、SyndicationFeed を非同期的に返す RetrieveBlogFeedAsync 関数を示します。In this next example we wrap a call to RetrieveFeedAsync, for a specific URI, to give us a RetrieveBlogFeedAsync function that asynchronously returns a SyndicationFeed.

// 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 を返します。In the example above, RetrieveBlogFeedAsync returns an IAsyncOperationWithProgress, which has both progress and a return value. RetrieveBlogFeedAsync が自分の処理を行い、フィードを取得している間は、他の作業を行うことができます。We can do other work while RetrieveBlogFeedAsync is doing its thing and retrieving the feed. 次に、非同期操作オブジェクトで get を呼び出し、ブロックするか完了まで待機して、操作結果を取得します。Then, we call get on that asynchronous operation object to block, wait for it to complete, and then obtain the results of the operation.

Windows ランタイム型を非同期的に返す場合は、IAsyncOperation<TResult> または IAsyncOperationWithProgress<TResult, TProgress> を返す必要があります。If you're asynchronously returning a Windows Runtime type, then you should return an IAsyncOperation<TResult> or an IAsyncOperationWithProgress<TResult, TProgress>. ファースト パーティ製またはサード パーティ製のランタイム クラス、または Windows ランタイム関数との間で受け渡しできる任意の型 (例、int または winrt::hstring) がこれに適合します。Any first- or third-party runtime class qualifies, or any type that can be passed to or from a Windows Runtime function (for example, int, or winrt::hstring). Windows ランタイム型以外でこれらの非同期操作型のいずれかを使用しようとすると、コンパイラからのサポートとして "T は WinRT 型である必要があります" というエラーが表示されます。The compiler will help you with a "T must be WinRT type" error if you try to use one of these asynchronous operation types with a non-Windows Runtime type.

コルーチンに 1 つ以上の co_await ステートメントがない場合、コルーチンであると認められるために、1 つ以上の co_return または 1 つの co_yield ステートメントが必要です。If a coroutine doesn't have at least one co_await statement then, in order to qualify as a coroutine, it must have at least one co_return or one co_yield statement. 非同期操作を必要とせず、そのためにコンテキストのブロックや切り替えを行わずに、コルーチンが値を返すことができる場合があります。There will be cases where your coroutine can return a value without introducing any asynchrony, and therefore without blocking nor switching context. 値をキャッシュすることでそれを行う例を次に示します (2 回目以降は呼び出されます)。Here's an example that does that (the second and subsequent times it's called) by caching a value.

winrt::hstring m_cache;

IAsyncOperation<winrt::hstring> ReadAsync()
{
    if (m_cache.empty())
    {
        // Asynchronously download and cache the string.
    }
    co_return m_cache;
}

Windows ランタイム型以外を非同期的に返すAsynchronously return a non-Windows-Runtime type

Windows ランタイム型以外の型を非同期的に返す場合は、並列パターン ライブラリ (PPL) concurrency::task を返す必要があります。If you're asynchronously returning a type that's not a Windows Runtime type, then you should return a Parallel Patterns Library (PPL) concurrency::task. std::futureよりもパフォーマンスに優れているため (また、今後の互換性の向上により)、concurrency::task をお勧めします。We recommend concurrency::task because it gives you better performance (and better compatibility going forward) than std::future does.

ヒント

<pplawait.h> を含めると、コルーチンの型として concurrency::task を使用できます。If you include <pplawait.h>, then you can use concurrency::task as a coroutine type.

// 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;
}

パラメーターの引き渡しParameter-passing

同期関数では、既定で const& パラメーターを使用する必要があります。For synchronous functions, you should use const& parameters by default. これにより、コピーのオーバーヘッドが回避されます (参照カウント、つまりインタロックされたインクリメントとデクリメントが含まれます)。That will avoid the overhead of copies (which involve reference counting, and that means interlocked increments and decrements).

// Synchronous function.
void DoWork(Param const& value);

ただし、コルーチンに参照パラメーターを渡した場合、問題が発生する可能性があります。But you can run into problems if you pass a reference parameter to a coroutine.

// 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.
}

コルーチンでは、最初の一時停止ポイントまでは実行は同期であり、そこで呼び出し元に制御が返され、呼び出しフレームが範囲外になります。In a coroutine, execution is synchronous up until the first suspension point, where control is returned to the caller and the calling frame goes out of scope. コルーチンが再開するまで、参照パラメーターが参照するソース値に何かが発生している可能性があります。By the time the coroutine resumes, anything might have happened to the source value that a reference parameter references. コルーチンの観点からは、参照パラメーターには管理されていない有効期間があります。From the coroutine's perspective, a reference parameter has uncontrolled lifetime. そのため、上記の例では、co_await までに安全にアクセスできますが、それ以降は安全ではありません。So, in the example above, we're safe to access value up until the co_await, but not after it. 呼び出し元によって "" が破棄された場合、その後にコルーチン内で値にアクセスしようとすると、メモリが破損する結果になります。In the event that value is destructed by the caller, attempting to access it inside the coroutine after that results in a memory corruption. また、その後その関数が中断し、再開した後でを使用しようとするリスクがある場合、DoOtherWorkAsync に安全にを渡すこともできません。Nor can we safely pass value to DoOtherWorkAsync if there's any risk that that function will in turn suspend and then try to use value after it resumes.

中断して再開した後で、パラメーターを安全に使用できるようにするには、コルーチンでは値渡しを既定で使用し、値によってキャプチャされて有効期間の問題が回避されるようにする必要があります。To make parameters safe to use after suspending and resuming, your coroutines should use pass-by-value by default to ensure that they capture by value, and avoid lifetime issues. それを行うことが安全であると確信できるためにそのガイダンスから逸脱できる場合は稀です。Cases when you can deviate from that guidance because you're certain that it's safe to do so are going to be rare.

// Coroutine
IASyncAction DoWorkAsync(Param value); // not const&

値で渡すには、引数の移動またはコピーが低コストである必要があり、通常はスマート ポインターを使用します。Passing by value requires that the argument be inexpensive to move or copy; and that's typically the case for a smart pointer.

(値を移動しない限り) 定数値を渡すことが良い方法であるということにも議論の余地があります。It's also arguable that (unless you want to move the value) passing by const value is good practice. コピー元のソース値に影響はありませんが、意図が明確になり、誤ってコピーを変更した場合に役立ちます。It won't have any effect on the source value from which you're making a copy, but it makes the intent clear, and helps if you inadvertently modify the copy.

// coroutine with strictly unnecessary const (but arguably good practice).
IASyncAction DoWorkAsync(Param const value);

非同期の呼び出し先に標準ベクターを渡す方法について説明した「標準的な配列とベクトル」も参照してください。Also see Standard arrays and vectors, which deals with how to pass a standard vector into an asynchronous callee.

コルーチンのシグネチャを変更することはできなくても、実装は変更できる場合は、最初の co_await の前にローカル コピーを作成することができます。If you can't change your coroutine's signature, but you can change the implementation, then you can make a local copy before the first 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 の前に、必要な部分だけを抽出します。If Param is expensive to copy, then extract just the pieces you need before the first 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).
}

class-member コルーチンで this ポインターに安全にアクセスするSafely accessing the this pointer in a class-member coroutine

C++/WinRT の強参照と弱参照」をご覧ください。See Strong and weak references in C++/WinRT.

重要な APIImportant APIs