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

このトピックではどちらもできる方法を作成およびと Windows ランタイムの非同期オブジェクトを使用C +/cli WinRTします。This topic shows the ways in which you can both create and consume Windows Runtime asynchronous objects with C++/WinRT.

非同期操作と 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 asychronous 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 +/cli 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.

呼び出しスレッドのブロック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.

注意

取得関数が c++ に存在する/cli WinRT プロジェクション型winrt::Windows::Foundation::IAsyncActionすべて C + 内から関数を呼び出すことができますので、/cli 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. メンバーとして記載されている関数は見つかりません、 IAsyncAction 取得アプリケーション バイナリ インターフェイス (ABI) の表面の一部でない、実際の Windows ランタイム型IAsyncActionします。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.

Windows ランタイム型を非同期的に返すAsychronously 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 ランタイム型以外でこれらの非同期操作型のいずれかを使用しようとすると表示される "WinRT 型である必要があります" というエラーをサポートします。The compiler will help you with a "must be WinRT type" error if you try to use one of these asychronous 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 ランタイム型以外を非同期的に返すAsychronously 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();

    // ...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. コルーチンが再開するまで、参照パラメーターが参照するソース値に何かが発生している可能性があります。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. また、その後その関数が中断し、再開した後でを使用しようとするリスクがある場合、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);

(値を移動しない限り) 定数値を渡すことが良い方法であるということにも議論の余地があります。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.

Windows スレッド プールへの処理のオフロードOffloading work onto the Windows thread pool

コルーチンは、関数に実行を返すまで、呼び出し元がブロックされていることなどの他の機能です。A coroutine is a function like any other in that a caller is blocked until a function returns execution to it. 返すコルーチンの最初のチャンスが 1 つ目とは、 co_awaitco_return、またはco_yieldします。And, the first opportunity for a coroutine to return is the first co_await, co_return, or co_yield.

そのため、実行する前にコルーチンでの作業を計算主体、実行を返す、呼び出し元にする必要があります (つまり、中断ポイントを導入する) 呼び出し元がブロックされないようにします。So, before you do compute-bound work in a coroutine, you need to return execution to the caller (in other words, introduce a suspension point) so that the caller isn't blocked. をまだ行っている場合co_await- その他のいくつか操作してかまいません ing co_await winrt::resume_background 関数。If you're not already doing that by co_await-ing some other operation, then you can co_await the winrt::resume_background function. これにより、呼び出し元に制御が返され、スレッド プールのスレッドですぐに実行が再開されます。That returns control to the caller, and then immediately resumes execution on a thread pool thread.

実装で使用されているスレッド プールは低レベルの Windows スレッド プールであるため、最適に効率化されます。The thread pool being used in the implementation is the low-level Windows thread pool, so it's optimially efficient.

IAsyncOperation<uint32_t> DoWorkOnThreadPoolAsync()
{
    co_await winrt::resume_background(); // Return control; resume on thread pool.

    uint32_t result;
    for (uint32_t y = 0; y < height; ++y)
    for (uint32_t x = 0; x < width; ++x)
    {
        // Do compute-bound work here.
    }
    co_return result;
}

スレッドの関係を考慮したプログラミングProgramming with thread affinity in mind

このシナリオは、前のシナリオをさらに詳しく説明しています。This scenario expands on the previous one. 一部の処理をスレッド プールにオフロードするが、ユーザー インターフェイス (UI) で進行状況を表示したいとします。You offload some work onto the thread pool, but then you want to display progress in the user interface (UI).

IAsyncAction DoWorkAsync(TextBlock textblock)
{
    co_await winrt::resume_background();
    // Do compute-bound work here.

    textblock.Text(L"Done!"); // Error: TextBlock has thread affinity.
}

上のコードは、winrt::hresult_wrong_thread 例外をスローします。これは、TextBlock がそれを作成したスレッド (UI スレッド) から更新する必要があるためです。The code above throws a winrt::hresult_wrong_thread exception, because a TextBlock must be updated from the thread that created it, which is the UI thread. 1 つの解決方法は、コルーチンが最初に呼び出されたスレッド コンテキストをキャプチャする方法です。One solution is to capture the thread context within which our coroutine was originally called. インスタンス化、 winrt::apartment_context オブジェクト、バック グラウンド作業をしco_awaitapartment_context呼び出し元に戻すコンテキスト。To do that, instantiate a winrt::apartment_context object, do background work, and then co_await the apartment_context to switch back to the calling context.

IAsyncAction DoWorkAsync(TextBlock textblock)
{
    winrt::apartment_context ui_thread; // Capture calling context.

    co_await winrt::resume_background();
    // Do compute-bound work here.

    co_await ui_thread; // Switch back to calling context.

    textblock.Text(L"Done!"); // Ok if we really were called from the UI thread.
}

上のコルーチンが TextBlock を作成した UI スレッドから呼び出される限り、この手法は機能します。As long as the coroutine above is called from the UI thread that created the TextBlock, then this technique works. アプリで多くの場合にそれを確信できます。There will be many cases in your app where you're certain of that.

一般的なソリューションは、呼び出し元のスレッドが不明確していない場合を対象とする、UI を更新することができますco_await winrt::resume_foreground スイッチを特定する関数フォア グラウンド スレッドです。For a more general solution to updating UI, which covers cases where you're uncertain about the calling thread, you can co_await the winrt::resume_foreground function to switch to a specific foreground thread. 次のコード例では、(Dispatcher プロパティにアクセスして) TextBlock に関連するディスパッチャー オブジェクトを渡すことでフォアグラウンド スレッドを指定しています。In the code example below, we specify the foreground thread by passing the dispatcher object associated with the TextBlock (by accessing its Dispatcher property). winrt::resume_foreground の実装では、そのディスパッチャー オブジェクトで CoreDispatcher.RunAsync を呼び出し、コルーチンでその後に続く処理を実行しています。The implementation of winrt::resume_foreground calls CoreDispatcher.RunAsync on that dispatcher object to execute the work that comes after it in the coroutine.

IAsyncAction DoWorkAsync(TextBlock textblock)
{
    co_await winrt::resume_background();
    // Do compute-bound work here.

    co_await winrt::resume_foreground(textblock.Dispatcher()); // Switch to the foreground thread associated with textblock.

    textblock.Text(L"Done!"); // Guaranteed to work.
}

実行コンテキスト、再開、およびコルーチンでの切り替えExecution contexts, resuming, and switching in a coroutine

大まかに言えば、コルーチンでの中断ポイントした後、元のスレッドの実行の終了してもよいし、再開に任意のスレッドで発生する可能性があります (つまり、任意のスレッドを呼び出すことができます、完了非同期操作のメソッド)。Broadly speaking, after a suspension point in a coroutine, the original thread of execution may go away and resumption may occur on any thread (in other words, any thread may call the Completed method for the async operation).

場合するco_await4 つの Windows ランタイムの非同期操作の種類のいずれか (IAsyncXxx)、し、C++/cli WinRT はポイントで呼び出し元のコンテキストをキャプチャするco_awaitします。But if you co_await any of the four Windows Runtime asynchronous operation types (IAsyncXxx), then C++/WinRT captures the calling context at the point you co_await. および継続タスクを再開したとき、そのコンテキストにまだ残っていることになります。And it ensures that you're still on that context when the continuation resumes. C +/cli WinRT は呼び出し元のコンテキストに既にいるかどうかをチェックし、そうでない場合は、これに切り替えることによって。C++/WinRT does this by checking whether you're already on the calling context and, if not, switching to it. したかどうかは、前に、シングル スレッド アパートメント (STA) スレッドでco_await、ことと同じものに後で; したかどうかは、前に、マルチ スレッド アパートメント (MTA) スレッドでco_await、後でいずれかにあります。If you were on a single-threaded apartment (STA) thread before co_await, then you'll be on the same one afterward; if you were on a multi-threaded apartment (MTA) thread before co_await, then you'll be on one afterward.

IAsyncAction ProcessFeedAsync()
{
    Uri rssFeedUri{ L"https://blogs.windows.com/feed" };
    SyndicationClient syndicationClient;

    // The thread context at this point is captured...
    SyndicationFeed syndicationFeed{ co_await syndicationClient.RetrieveFeedAsync(rssFeedUri) };
    // ...and is restored at this point.
}

この動作に依存できる理由は、ためには C +/cli WinRT C++ コルーチンの言語サポート (これらのコードは待機のアダプターで呼ばれます) に、Windows ランタイム非同期操作の種類を調整するためのコードを提供します。The reason you can rely on this behavior is because C++/WinRT provides code to adapt those Windows Runtime asynchronous operation types to the C++ coroutine language support (these pieces of code are called wait adapters). 残りの待機可能な型に C +/cli WinRT はスレッド プール ラッパーやヘルパー。そのため、スレッド プールで完了します。The remaining awaitable types in C++/WinRT are simply thread pool wrappers and/or helpers; so they complete on the thread pool.

using namespace std::chrono;
IAsyncOperation<int> return_123_after_5s()
{
    // No matter what the thread context is at this point...
    co_await 5s;
    // ...we're on the thread pool at this point.
    co_return 123;
}

場合するco_await他の何らかの種類—C + 内でも/cli WinRT コルーチンの実装—別のライブラリは、アダプターを提供し、再開とコンテキストの観点からこれらのアダプターが行う操作を理解する必要があります。If you co_await some other type—even within a C++/WinRT coroutine implementation—then another library provides the adapters, and you'll need to understand what those adapters do in terms of resumption and contexts.

コンテキストの切り替えを最小値までを保持するには、このトピックで前述したよう手法の一部を使用できます。To keep context switches down to a minimum, you can use some of the techniques that we've already seen in this topic. これを行うのいくつかの図を見てみましょう。Let's see some illustrations of doing that. この次の擬似コードの例では、イベント ハンドラー、イメージを読み込むための Windows ランタイム API を呼び出すし、そのイメージを処理するバック グラウンド スレッドにドロップし、UI でイメージを表示する UI スレッドに戻りますのアウトラインを説明します。In this next pseudo-code example, we show the outline of an event handler that calls a Windows Runtime API to load an image, drops onto a background thread to process that image, and then returns to the UI thread to display the image in the UI.

IAsyncAction MainPage::ClickHandler(IInspectable /* sender */, RoutedEventArgs /* args */)
{
    // We begin in the UI context.

    // Call StorageFile::OpenAsync to load an image file.

    // The call to OpenAsync occurred on a background thread, but C++/WinRT has restored us to the UI thread by this point.

    co_await winrt::resume_background();

    // We're now on a background thread.

    // Process the image.

    co_await winrt::resume_foreground(this->Dispatcher());

    // We're back on MainPage's UI thread.

    // Display the image in the UI.
}

このシナリオでは、少し呼び出しの周囲の ineffiency StorageFile::OpenAsyncします。For this scenario, there's a little bit of ineffiency around the call to StorageFile::OpenAsync. 背景に必要なコンテキスト スイッチがある (そのハンドラーは、実行を呼び出し元に戻すことができる) スレッド、再開後にどの C +/cli WinRT が UI スレッドのコンテキストを復元します。There's a necessary context switch to a background thread (so that the handler can return execution to the caller), on resumption after which C++/WinRT restores the UI thread context. しかし、ここでは、UI を更新しようとしていることになるまで、UI スレッド上に存在する必要はありません。But, in this case, it's not necessary to be on the UI thread until we're about to update the UI. いわゆる複数の Windows ランタイム Apiする前にへの呼び出しをwinrt::resume_backgroundより不要な - 前後のコンテキスト スイッチが発生しています。The more Windows Runtime APIs we call before our call to winrt::resume_background, the more unnecessary back-and-forth context switches we incur. ソリューションは呼び出す任意その前に、Windows ランタイム Api です。The solution is not to call any Windows Runtime APIs before then. すべての後に移動、 winrt::resume_backgroundします。Move them all after the winrt::resume_background.

IAsyncAction MainPage::ClickHandler(IInspectable /* sender */, RoutedEventArgs /* args */)
{
    // We begin in the UI context.

    co_await winrt::resume_background();

    // We're now on a background thread.

    // Call StorageFile::OpenAsync to load an image file.

    // Process the image.

    co_await winrt::resume_foreground(this->Dispatcher());

    // We're back on MainPage's UI thread.

    // Display the image in the UI.
}

独自に記述する可能性がありより高度なものを行いたい場合は、アダプターを待機します。If you want to do something more advanced, then you could write your own await adapters. たい場合など、co_awaitで非同期操作が完了するのと同じスレッドで再開する (そのためがないコンテキストの切り替え) を記述して開始する可能性がありますし、await の次に示すようなアダプター。For example, if you want a co_await to resume on the same thread that the async action completes on (so, there's no context switch), then you could begin by writing await adapters similar to the ones shown below.

注意

教育目的のみで、次のコード例が提供されます。作業を開始するのには理解がどのアダプターの連携を待機します。The code example below is provided for educational purposes only; it's to get you started understanding how await adapters work. 開発および、独自にテストすることをお勧めし、独自のコードベースでは、この手法を使用する場合は、アダプター struct(s) を待機します。If you want to use this technique in your own codebase, then we recommend that you develop and test your own await adapter struct(s). たとえば、作成するcomplete_on_anycomplete_on_current、およびcomplete_on(dispatcher) します。For example, you could write complete_on_any, complete_on_current, and complete_on(dispatcher). 受け取るテンプレートにすることも検討、 IAsyncXxx型テンプレート パラメーターとして。Also consider making them templates that take the IAsyncXxx type as a template parameter.

struct no_switch
{
    no_switch(Windows::Foundation::IAsyncAction const& async) : m_async(async)
    {
    }

    bool await_ready() const
    {
        return m_async.Status() == Windows::Foundation::AsyncStatus::Completed;
    }

    void await_suspend(std::experimental::coroutine_handle<> handle) const
    {
        m_async.Completed([handle](Windows::Foundation::IAsyncAction const& /* asyncInfo */, Windows::Foundation::AsyncStatus const& /* asyncStatus */)
        {
            handle();
        });
    }

    auto await_resume() const
    {
        return m_async.GetResults();
    }

private:
    Windows::Foundation::IAsyncAction const& m_async;
};

使用する方法を理解する、切り替えawait アダプター、することを確認する必要がありますのではまず、C++コンパイラが検出、co_await呼び出された関数が検索式await_readyawait_suspend、およびawait_resumeします。To understand how to use the no_switch await adapters, you'll first need to know that when the C++ compiler encounters a co_await expression it looks for functions called await_ready, await_suspend, and await_resume. C++/cli WinRT ライブラリは、既定では、このような適切な動作を取得するためにこれらの関数を提供します。The C++/WinRT library provides those functions so that you get reasonable behavior by default, like this.

IAsyncAction async{ ProcessFeedAsync() };
co_await async;

使用する、切り替えawait アダプター、その型を変更するだけco_awaitから式IAsyncXxx切り替え、次のようにします。To use the no_switch await adapters, just change the type of that co_await expression from IAsyncXxx to no_switch, like this.

IAsyncAction async{ ProcessFeedAsync() };
co_await static_cast<no_switch>(async);

次の 3 つの検索ではなく、 await_xxx関数と一致するIAsyncXxx、C++コンパイラと一致する関数を探します切り替えします。Then, instead of looking for the three await_xxx functions that match IAsyncXxx, the C++ compiler looks for functions that match no_switch.

非同期操作、および取り消しのコールバックをキャンセルCanceling an asychronous operation, and cancellation callbacks

非同期プログラミング向けの Windows ランタイムの機能を使用すると、実行中の非同期アクションまたは操作をキャンセルできます。The Windows Runtime's features for asynchronous programming allow you to cancel an in-flight asynchronous action or operation. 呼び出す例を次に示します StorageFolder::GetFilesAsync が大きくなる可能性、ファイルのコレクションを取得するには、データ メンバーの非同期操作の結果のオブジェクトを格納します。Here's an example that calls StorageFolder::GetFilesAsync to retrieve a potentially large collection of files, and it stores the resulting asynchronous operation object in a data member. ユーザーには、操作をキャンセルするオプションがあります。The user has the option to cancel the operation.

// MainPage.xaml
...
<Button x:Name="workButton" Click="OnWork">Work</Button>
<Button x:Name="cancelButton" Click="OnCancel">Cancel</Button>
...

// MainPage.h
...
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Storage.Search.h>

using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Foundation::Collections;
using namespace Windows::Storage;
using namespace Windows::Storage::Search;
using namespace Windows::UI::Xaml;
...
struct MainPage : MainPageT<MainPage>
{
    MainPage()
    {
        InitializeComponent();
    }

    IAsyncAction OnWork(IInspectable /* sender */, RoutedEventArgs /* args */)
    {
        workButton().Content(winrt::box_value(L"Working..."));

        // Enable the Pictures Library capability in the app manifest file.
        StorageFolder picturesLibrary{ KnownFolders::PicturesLibrary() };

        m_async = picturesLibrary.GetFilesAsync(CommonFileQuery::OrderByDate, 0, 1000);

        IVectorView<StorageFile> filesInFolder{ co_await m_async };

        workButton().Content(box_value(L"Done!"));

        // Process the files in some way.
    }

    void OnCancel(IInspectable const& /* sender */, RoutedEventArgs const& /* args */)
    {
        if (m_async.Status() != AsyncStatus::Completed)
        {
            m_async.Cancel();
            workButton().Content(winrt::box_value(L"Canceled"));
        }
    }

private:
    IAsyncOperation<::IVectorView<StorageFile>> m_async;
};
...

キャンセルの実装側では、簡単な例から始めましょう。For the implementation side of cancellation, let's begin with a simple example.

// main.cpp
#include <iostream>
#include <winrt/Windows.Foundation.h>

using namespace winrt;
using namespace Windows::Foundation;
using namespace std::chrono_literals;

IAsyncAction ImplicitCancellationAsync()
{
    while (true)
    {
        std::cout << "ImplicitCancellationAsync: do some work for 1 second" << std::endl;
        co_await 1s;
    }
}

IAsyncAction MainCoroutineAsync()
{
    auto implicit_cancellation{ ImplicitCancellationAsync() };
    co_await 3s;
    implicit_cancellation.Cancel();
}

int main()
{
    winrt::init_apartment();
    MainCoroutineAsync().get();
}

上記の例を実行するかどうかは、表示ImplicitCancellationAsync 3 秒間、その後に自動的に終了しますその結果れるキャンセルの 1 秒あたりの 1 つのメッセージを出力します。If you run the example above, then you'll see ImplicitCancellationAsync print one message per second for three seconds, after which time it automatically terminates as a result of being canceled. 発生したため、これは、機能、co_awaitが取り消されましたかどうか、式、コルーチンを確認します。This works because, on encountering a co_await expression, a coroutine checks whether it has been cancelled. 場合は、し、それを実行せずにアウトしてください。これに該当しない場合は、これを中断し、通常どおりです。If it has, then it short-circuits out; and if it hasn't, then it suspends as normal.

キャンセルは、コルーチンを中断している間、発生します。Cancellation can, of course, happen while the coroutine is suspended. コルーチンの再開時にのみヒット別またはco_await、これは取り消し状態をチェックします。Only when the coroutine resumes, or hits another co_await, will it check for cancellation. 問題では、キャンセルに応答する可能性のあるすぎる粗い-詳細の待機時間の 1 つです。The issue is one of potentially too-coarse-grained latency in responding to cancellation.

そのため、別のオプションは、コルーチン内からのキャンセルを明示的にポーリングします。So, another option is to explicitly poll for cancellation from within your coroutine. 以下のリスト内のコードでは、上記の例を更新します。Update the example above with the code in the listing below. この新しい例ではExplicitCancellationAsyncによって返されるオブジェクトを取得、 winrt::get_cancellation_token 関数は、オブジェクトを定期的に使用してコルーチンが取り消されたかどうかを確認します。In this new example, ExplicitCancellationAsync retrieves the object returned by the winrt::get_cancellation_token function, and uses it to periodically check whether the coroutine has been canceled. コルーチンが無制限にループ処理が取り消されない限り、キャンセルすると、ループ、関数は、通常どおりに終了します。As long as it's not canceled, the coroutine loops indefinitely; once it is canceled, the loop and the function exit normally. 結果は、ここで終了するが、前の例では、明示的が行われるため、管理下にある同じです。The outcome is the same as the previous example, but here exiting happens explicitly, and under control.

IAsyncAction ExplicitCancellationAsync()
{
    auto cancellation_token{ co_await winrt::get_cancellation_token() };

    while (!cancellation_token())
    {
        std::cout << "ExplicitCancellationAsync: do some work for 1 second" << std::endl;
        co_await 1s;
    }
}

IAsyncAction MainCoroutineAsync()
{
    auto explicit_cancellation{ ExplicitCancellationAsync() };
    co_await 3s;
    explicit_cancellation.Cancel();
}
...

待機しているwinrt::get_cancellation_tokenの知識があるキャンセル トークンを取得、 IAsyncActionコルーチンが自動的に生成します。Waiting on winrt::get_cancellation_token retrieves a cancellation token with knowledge of the IAsyncAction that the coroutine is producing on your behalf. そのトークンに関数呼び出し演算子を使用するにはキャンセル状態を照会する—本質的に取り消し状態をポーリングします。You can use the function call operator on that token to query the cancellation state—essentially polling for cancellation. 大規模なコレクションを反復し、これは、適切な手法によって計算バウンドの操作を実行している場合。If you're performing some compute-bound operation, or iterating through a large collection, then this is a reasonable technique.

キャンセル コールバックを登録します。Register a cancellation callback

Windows ランタイムの取り消しは、その他の非同期オブジェクトを自動的に流れません。The Windows Runtime's cancellation doesn't automatically flow to other asynchronous objects. —10.0.17763.0 (Windows 10、バージョンは 1809) のバージョンの Windows SDK で導入された—取り消しのコールバックを登録することができます。But—introduced in version 10.0.17763.0 (Windows 10, version 1809) of the Windows SDK—you can register a cancellation callback. これは、プリエンプティブなフックをキャンセルを伝えることができるし、同時実行の既存のライブラリと統合することになります。This is a pre-emptive hook by which cancellation can be propagated, and makes it possible to integrate with existing concurrency libraries.

この次のコード例ではNestedCoroutineAsync作業が特別な取り消しロジックがありません。In this next code example, NestedCoroutineAsync does the work, but it has no special cancellation logic in it. CancellationPropagatorAsync ; 入れ子になったコルーチンのラッパーでは基本的には、ラッパーがにわかキャンセルを転送します。CancellationPropagatorAsync is essentially a wrapper on the nested coroutine; the wrapper forwards cancellation pre-emptively.

// main.cpp
#include <iostream>
#include <winrt/Windows.Foundation.h>

using namespace winrt;
using namespace Windows::Foundation;
using namespace std::chrono_literals;

IAsyncAction NestedCoroutineAsync()
{
    while (true)
    {
        std::cout << "NestedCoroutineAsync: do some work for 1 second" << std::endl;
        co_await 1s;
    }
}

IAsyncAction CancellationPropagatorAsync()
{
    auto cancellation_token{ co_await winrt::get_cancellation_token() };
    auto nested_coroutine{ NestedCoroutineAsync() };

    cancellation_token.callback([=]
    {
        nested_coroutine.Cancel();
    });

    co_await nested_coroutine;
}

IAsyncAction MainCoroutineAsync()
{
    auto cancellation_propagator{ CancellationPropagatorAsync() };
    co_await 3s;
    cancellation_propagator.Cancel();
}

int main()
{
    winrt::init_apartment();
    MainCoroutineAsync().get();
}

CancellationPropagatorAsyncレジスタの独自のキャンセル コールバックし、ラムダ関数を待機 (が中断されます)、入れ子になった作業が完了するまでです。CancellationPropagatorAsync registers a lambda function for its own cancellation callback, and then it awaits (it suspends) until the nested work completes. 場合、またはCancellationPropagatorAsyncが取り消されると、入れ子になったコルーチンにキャンセルを伝達します。When or if CancellationPropagatorAsync is canceled, it propagates the cancellation to the nested coroutine. キャンセル; をポーリングする必要はありません。キャンセルは無限にブロックします。There's no need to poll for cancellation; nor is cancellation blocked indefinitely. このメカニズムはコルーチンまたは同時実行の何も認識しているライブラリとの相互運用に使用するための十分な柔軟性/cli WinRT します。This mechanism is flexible enough for you to use it to interop with a coroutine or concurrency library that knows nothing of C++/WinRT.

進行状況の報告Reporting progress

場合は、コルーチンでは、どちらかを返します IAsyncActionWithProgress、または IAsyncOperationWithProgress、取得することができ、によって返されるオブジェクト、 winrt::get_progress_token 関数、および進行状況のハンドラーに進行状況の報告に使用します。If your coroutine returns either IAsyncActionWithProgress, or IAsyncOperationWithProgress, then you can retrieve the object returned by the winrt::get_progress_token function, and use it to report progress back to a progress handler. 次にコード例を示します。Here's a code example.

// main.cpp
#include <iostream>
#include <winrt/Windows.Foundation.h>

using namespace winrt;
using namespace Windows::Foundation;
using namespace std::chrono_literals;

IAsyncOperationWithProgress<double, double> CalcPiTo5DPs()
{
    auto progress{ co_await winrt::get_progress_token() };

    co_await 1s;
    double pi_so_far{ 3.1 };
    progress(0.2);

    co_await 1s;
    pi_so_far += 4.e-2;
    progress(0.4);

    co_await 1s;
    pi_so_far += 1.e-3;
    progress(0.6);

    co_await 1s;
    pi_so_far += 5.e-4;
    progress(0.8);

    co_await 1s;
    pi_so_far += 9.e-5;
    progress(1.0);
    co_return pi_so_far;
}

IAsyncAction DoMath()
{
    auto async_op_with_progress{ CalcPiTo5DPs() };
    async_op_with_progress.Progress([](auto const& /* sender */, double progress)
    {
        std::wcout << L"CalcPiTo5DPs() reports progress: " << progress << std::endl;
    });
    double pi{ co_await async_op_with_progress };
    std::wcout << L"CalcPiTo5DPs() is complete !" << std::endl;
    std::wcout << L"Pi is approx.: " << pi << std::endl;
}

int main()
{
    winrt::init_apartment();
    DoMath().get();
}

注意

1 つ以上実装するために正しくない完了ハンドラー非同期アクションまたは操作します。It's not correct to implement more than one completion handler for an asynchronous action or operation. 、Completed イベントの 1 つのデリゲートがあることもできますco_awaitこと。You can have either a single delegate for its completed event, or you can co_await it. 両方がある場合は、2 つ目は失敗します。If you have both, then the second will fail. いずれか適切な; 次の 2 種類の完了ハンドラーの 1 つです両方ではなく、同じ非同期オブジェクトの。Either one of the following two kinds of completion handlers is appropriate; not both for the same async object.

auto async_op_with_progress{ CalcPiTo5DPs() };
async_op_with_progress.Completed([](auto const& sender, AsyncStatus /* status */)
{
    double pi{ sender.GetResults() };
});
auto async_op_with_progress{ CalcPiTo5DPs() };
double pi{ co_await async_op_with_progress };

完了ハンドラーの詳細については、次を参照してください。非同期アクションおよび操作の種類をデリゲートします。For more info about completion handlers, see Delegate types for asynchronous actions and operations.

ファイア アンド フォーゲットFire and forget

場合によっては、他の作業と同時に実行できるタスクがあるし、そのタスクを完了するまで待機する必要はありません (その他の作業なしに依存)、その値を返す必要があるとします。Sometimes, you have a task that can be done concurrently with other work, and you don't need to wait for that task to complete (no other work depends on it), nor do you need it to return a value. その場合は、タスクを起動し、忘れたできます。In that case, you can fire off the task and forget it. コルーチンの戻り値の型を記述することができます winrt::fire_and_forget (Windows ランタイムの非同期操作の種類のいずれかではなくまたはconcurrency::task).You can do that by writing a coroutine whose return type is winrt::fire_and_forget (instead of one of the Windows Runtime asynchronous operation types, or concurrency::task).

// main.cpp
#include <winrt/Windows.Foundation.h>

using namespace winrt;
using namespace std::chrono_literals;

winrt::fire_and_forget CompleteInFiveSeconds()
{
    co_await 5s;
}

int main()
{
    winrt::init_apartment();
    CompleteInFiveSeconds();
    // Do other work here.
}

重要な APIImportant APIs