C++ での UWP アプリの非同期操作の作成

このドキュメントでは、タスク クラスを使用して、ユニバーサル Windows ランタイム (UWP) アプリの Windows ThreadPool ベースの非同期操作を生成する際に留意すべき重要な点について説明します。

非同期プログラミングを使用すると、アプリはユーザー入力への応答性を維持できるため、これは Windows ランタイム アプリ モデルの重要なコンポーネントです。 UI スレッドをブロックすることなく、長時間実行されるタスクを開始することができ、後でタスクの結果を受け取ることができます。 また、タスクを取り消したり、タスクがバックグラウンドで実行される間に進行状況の通知を受け取ることができます。 C++ での非同期プログラミングに関するドキュメントでは、UWP アプリを作成するために Visual C++ で使用できる非同期パターンの概要を説明しています。 そのドキュメントでは、Windows ランタイムの非同期操作のチェーンの使用と作成の両方の方法を解説しています。 このセクションでは、ppltasks.h の型を使用して、別の Windows ランタイム コンポーネントで使用できる非同期操作を生成する方法と、非同期処理の実行方法を制御する方法について説明します。 また、「Hilo (C++ と XAML を使用した Windows ストア アプリ) での非同期プログラミングのパターンとヒント」を読んで、Hilo (C++ と XAML を使用した Windows ランタイム アプリ) でタスク クラスを使用して非同期操作を実装する方法も学習することを検討してください。

Note

UWP アプリでは、並列パターン ライブラリ (PPL) と非同期エージェント ライブラリを使用できます。 ただし、タスク スケジューラとリソース マネージャーを使用することはできません。 このドキュメントでは、PPL で提供される追加機能のうち、デスクトップ アプリではなく、UWP アプリでのみ使用できる機能について説明します。

重要なポイント

  • 他のコンポーネントで使用する非同期操作 (C++ 以外の言語で記述される場合があります) を作成するには、concurrency::create_async を使用します。

  • 非同期操作を呼び出すコンポーネントに進行状況の通知をレポートするためには、concurrency::progress_reporter を使用します。

  • 内部非同期操作の取り消しを可能にするには、キャンセル トークンを使用します。

  • create_async の関数の動作は、渡される処理関数の戻り値の型によって異なります。 タスク ( task<T> または task<void>) を返す処理関数は、 create_asyncを呼び出したコンテキストで同期的に実行されます。 T または void を返す処理関数は、任意のコンテキストで実行されます。

  • concurrency::task::then メソッドを使用すると、順次実行タスクのチェーンを作成できます。 UWP アプリでは、タスクの継続の既定のコンテキストは、そのタスクが構築された方法によって異なります。 非同期アクションをタスク コンストラクターに渡すことによってタスクが作成されている場合、または非同期アクションを返すラムダ式を渡すことによってタスクが作成されている場合は、そのタスクのすべての継続の既定のコンテキストは、現在のコンテキストです。 タスクが非同期のアクションから構築されていない場合、タスクの継続には任意のコンテキストが既定で使用されます。 既定のコンテキストを concurrency::task_continuation_context クラスでオーバーライドできます。

このドキュメントの内容

非同期操作を作成する

並列パターン ライブラリ (PPL) でタスクや継続のモデルを使用して、バックグラウンド タスクを定義したり、前のタスクが完了すると実行される追加のタスクを定義することができます。 この機能は concurrency::task クラスによって提供されます。 このモデルの詳細および task クラスの詳細については、「 Task Parallelismを呼び出したコンテキストで同期的に実行されます。

Windows ランタイムは、特別なオペレーティング システム環境でのみ動作する UWP アプリを作成する際に使用できるプログラミング インターフェイスです。 このようなアプリは、認定された関数、データ型、デバイスを使い、Microsoft Store から配布されます。 Windows ランタイムは、"アプリケーション バイナリ インターフェイス" (ABI) で表されます。 ABI は、Windows ランタイム API を Visual C++ などのプログラミング言語で使用できるようにするための基になるバイナリ コントラクトです。

Windows ランタイムを使用すると、さまざまなプログラミング言語の最適な機能を使用し、それらを 1 つのアプリにまとめることができます。 たとえば、JavaScript で UI を作成し、C ++ のコンポーネントで計算量が非常に多い演算を行うことができます。 計算量が非常に多い演算をバックグラウンドで行うことができるのは、UI の応答性を保つための重要な要素です。 task クラスは C++ に固有であるため、Windows ランタイム インターフェイスを使用して、(C++ 以外の言語で記述されている可能性のある) 他のコンポーネントに非同期操作を伝える必要があります。 Windows ランタイムには、非同期操作を表すために使用できる 4 つのインターフェイスが用意されています。

Windows::Foundation::IAsyncAction
非同期アクションを表します。

Windows::Foundation::IAsyncActionWithProgress<TProgress>
進行状況を報告する非同期アクションを表します。

Windows::Foundation::IAsyncOperation<TResult>
結果を返す非同期操作を表します。

Windows::Foundation::IAsyncOperationWithProgress<TResult, TProgress>
結果を返し、進行状況を報告する、非同期操作を表します。

アクション の概念は、非同期タスクが値を生成しないことを意味します ( voidを返す関数を考えてみてください)。 操作 の概念は、非同期タスクが値を生成することを意味します。 進行状況 の概念は、タスクが呼び出し元に進行状況を報告できることを意味します。 JavaScript、.NET Framework および Visual C++ はそれぞれ、ABI の境界を越えて使用するため、これらのインターフェイスのインスタンスを作成する独自の方法を提供します。 Visual C++ では、PPL は concurrency::create_async 関数を提供します。 この関数では、タスクの完了を表す Windows ランタイムの非同期アクションまたは操作を作成します。 create_async 関数は、処理関数 (通常はラムダ式) を受け取り、内部的に task オブジェクトを作成し、4 つの非同期 Windows ランタイム インターフェイスのいずれかにそのタスクをラップします。

Note

別の言語または別の Windows ランタイム コンポーネントからアクセスできる機能を作成する必要がある場合にのみ、create_async を使用します。 同じコンポーネントの C++ コードで操作が生成、実行されることがわかっている場合には、 task クラスを直接使用します。

create_async の戻り値の型は、引数の型によって決まります。 たとえば、作業関数が値を返さず、進行状況を報告しない場合、 create_asyncIAsyncActionを返します。 作業関数が値を返さず、進行状況を報告する場合、 create_asyncIAsyncActionWithProgressを返します。 進行状況を報告するには、作業関数のパラメーターとして concurrency::progress_reporter オブジェクトを提供します。 進行状況を報告する機能により、実行された作業量と残りの作業量を報告できます (たとえば、パーセントにより)。 結果が使用できるようになったらそれを報告することができます。

IAsyncActionIAsyncActionWithProgress<TProgress>IAsyncOperation<TResult>、および IAsyncActionOperationWithProgress<TProgress, TProgress> インターフェイスはそれぞれ、非同期操作を取り消すことができるように Cancel メソッドを提供しています。 task クラスは、キャンセル トークンを使用します。 作業を取り消すためにキャンセル トークンを使用すると、ランタイムはそのトークンをサブスクライブする新しい作業を開始しません。 既にアクティブである作業はそのキャンセル トークンを監視でき、可能な場合には停止できます。 この機構については、ドキュメント「 Cancellation in the PPL」で詳しく説明します。 タスクの取り消しを Windows ランタイムの Cancel メソッドに結び付ける方法は 2 つあります。 最初に、create_async に渡す処理関数が concurrency::cancellation_token オブジェクトとなるように定義できます。 Cancel メソッドが呼び出されると、このキャンセル トークンが取り消され、create_async の呼び出しをサポートする基になる task オブジェクトに、通常の取り消し規則が適用されます。 cancellation_token オブジェクトを指定しない場合、基になる task オブジェクトが暗黙的に定義します。 処理関数の取り消しに協調的に応答する必要がある場合は cancellation_token オブジェクトを定義します。 「例: C++ と XAML を使用した Windows ランタイム アプリでの実行の制御」では、カスタムの Windows ランタイム C++ コンポーネントを使用する、C# と XAML を使ったユニバーサル Windows プラットフォーム (UWP) アプリで取り消し処理を実行する方法の例を示しています。

警告

タスクの継続のチェーンでは、キャンセル トークンが取り消された場合に、常に状態をクリーンアップしてから、concurrency::cancel_current_task を呼び出します。 cancel_current_taskを呼び出す代わりにすぐに制御を返す場合は、操作は、取り消された状態でなく、完了の状態に遷移します。

次の表では、アプリケーションの非同期操作を定義するために使用できる組み合わせを示します。

この Windows ランタイム インターフェイスを作成する create_async これらのパラメーターの型を処理関数に渡して、暗黙的なキャンセル トークンを使用する これらのパラメーターの型を処理関数に渡して、明示的なキャンセル トークンを使用する
IAsyncAction void または task<void> (なし) (cancellation_token)
IAsyncActionWithProgress<TProgress> void または task<void> (progress_reporter) (progress_reporter, cancellation_token)
IAsyncOperation<TResult> T または task<T> (なし) (cancellation_token)
IAsyncActionOperationWithProgress<TProgress, TProgress> T または task<T> (progress_reporter) (progress_reporter, cancellation_token)

task 関数に渡す処理関数から、値または create_async オブジェクトを返すことができます。 これらのバリエーションによって異なる動作になります。 値が返されたら、処理関数はバックグラウンド スレッドで実行できるように、 task にラップされます。 また、基になる task は、暗黙的なキャンセル トークンを使用します。 逆に task オブジェクトを返した場合、処理関数は同期的に実行されます。 したがって task オブジェクトを返す場合は、処理関数で時間のかかる操作はすべてタスクとして実行され、アプリケーションの応答性を保てるようにします。 また、基になる task は、暗黙的なキャンセル トークンを使用しません。 したがって、 cancellation_token から task オブジェクトを返す場合に取り消しをサポートする必要がある場合、 create_asyncオブジェクトを受け取るように処理関数を定義する必要があります。

次の例は、別の Windows ランタイム コンポーネントで使用できる IAsyncAction オブジェクトを作成するさまざまな方法を示しています。

// Creates an IAsyncAction object and uses an implicit cancellation token.
auto op1 = create_async([]
{
    // Define work here.
});

// Creates an IAsyncAction object and uses no cancellation token.
auto op2 = create_async([]
{
    return create_task([]
    {
        // Define work here.
    });
});

// Creates an IAsyncAction object and uses an explicit cancellation token.
auto op3 = create_async([](cancellation_token ct)
{
    // Define work here.
});

// Creates an IAsyncAction object that runs another task and also uses an explicit cancellation token.
auto op4 = create_async([](cancellation_token ct)
{
    return create_task([ct]()
    {
        // Define work here.
    });
});

例: C++ Windows ランタイム コンポーネントを作成し、C から使用する#

XAML と C# を使って UI を定義し、C++ Windows ランタイム コンポーネントを使ってコンピューティング集中型の操作を実行するアプリを考えてみます。 この例では、C++ コンポーネントは特定の範囲での素数を計算します。 4 つの Windows ランタイム非同期タスク インターフェイスの違いを示すために、まず、Visual Studio で空のソリューションを作成し、Primes という名前を付けます。 次に、ソリューションに [Windows ランタイム コンポーネント] プロジェクトを追加し、名前を PrimesLibraryとします。 生成された C++ ヘッダー ファイル (この例では Class1.h の名前を Primes.h に変更しています) に次のコードを追加します。 public の各メソッドは 4 つの非同期インターフェイスの 1 つを定義します。 値を返すメソッドは、Windows::Foundation::Collections::IVector<int> オブジェクトを返します。 進行状況を報告するメソッドは、全体の作業のうち完了した割合を定義する double の値を生成します。

#pragma once

namespace PrimesLibrary
{
    public ref class Primes sealed
    {
    public:
        Primes();

        // Computes the numbers that are prime in the provided range and stores them in an internal variable.
        Windows::Foundation::IAsyncAction^ ComputePrimesAsync(int first, int last);

        // Computes the numbers that are prime in the provided range and stores them in an internal variable.
        // This version also reports progress messages.
        Windows::Foundation::IAsyncActionWithProgress<double>^ ComputePrimesWithProgressAsync(int first, int last);

        // Gets the numbers that are prime in the provided range.
        Windows::Foundation::IAsyncOperation<Windows::Foundation::Collections::IVector<int>^>^ GetPrimesAsync(int first, int last);

        // Gets the numbers that are prime in the provided range. This version also reports progress messages.
        Windows::Foundation::IAsyncOperationWithProgress<Windows::Foundation::Collections::IVector<int>^, double>^ GetPrimesWithProgressAsync(int first, int last);
    };
}

Note

慣例により、Windows ランタイムの非同期メソッド名は、通常 "Async" で終わります。

生成された C++ ソース ファイル (この例では Class1.cpp の名前を Primes.cpp に変更しています) に次のコードを追加します。 is_prime 関数は、入力が素数かどうかを判定します。 残りのメソッドは Primes クラスを実装します。 create_async への呼び出しはそれぞれ、呼び出されるメソッドと互換性のあるシグネチャを使用します。 たとえば、 Primes::ComputePrimesAsyncIAsyncActionを返すため、 create_async に指定された処理関数は値を返さず、パラメーターとして progress_reporter オブジェクトを受け取りません。

// PrimesLibrary.cpp
#include "pch.h"
#include "Primes.h"
#include <atomic>
#include <collection.h>
#include <ppltasks.h>
#include <concurrent_vector.h>

using namespace concurrency;
using namespace std;

using namespace Platform;
using namespace Platform::Collections;
using namespace Windows::Foundation;
using namespace Windows::Foundation::Collections;

using namespace PrimesLibrary;

Primes::Primes()
{
}

// Determines whether the input value is prime. 
bool is_prime(int n)
{
    if (n < 2)
    {
        return false;
    }
    for (int i = 2; i < n; ++i)
    {
        if ((n % i) == 0)
        {
            return false;
        }
    }
    return true;
}

// Adds the numbers that are prime in the provided range  
// to the primes global variable.
IAsyncAction^ Primes::ComputePrimesAsync(int first, int last)
{
    return create_async([this, first, last]
    {
        // Ensure that the input values are in range. 
        if (first < 0 || last < 0)
        {
            throw ref new InvalidArgumentException();
        }
        // Perform the computation in parallel.
        parallel_for(first, last + 1, [this](int n)
        {
            if (is_prime(n))
            {
                // Perhaps store the value somewhere...
            }
        });
    });
}

IAsyncActionWithProgress<double>^ Primes::ComputePrimesWithProgressAsync(int first, int last)
{
    return create_async([first, last](progress_reporter<double> reporter)
    {
        // Ensure that the input values are in range.
        if (first < 0 || last < 0)
        {
            throw ref new InvalidArgumentException();
        }
        // Perform the computation in parallel. 
        atomic<long> operation = 0;
        long range = last - first + 1;
        double lastPercent = 0.0;
        parallel_for(first, last + 1, [&operation, range, &lastPercent, reporter](int n)
        {
            // Report progress message.
            double progress = 100.0 * (++operation) / range;
            if (progress >= lastPercent)
            {
                reporter.report(progress);
                lastPercent += 1.0;
            }

            if (is_prime(n))
            {
                // Perhaps store the value somewhere...
            }
        });
        reporter.report(100.0);
    });
}

IAsyncOperation<IVector<int>^>^ Primes::GetPrimesAsync(int first, int last)
{
    return create_async([this, first, last]() -> IVector<int>^
    {
        // Ensure that the input values are in range. 
        if (first < 0 || last < 0)
        {
            throw ref new InvalidArgumentException();
        }
        // Perform the computation in parallel.
        concurrent_vector<int> primes;
        parallel_for(first, last + 1, [this, &primes](int n)
        {
            // If the value is prime, add it to the global vector.
            if (is_prime(n))
            {
                primes.push_back(n);
            }
        });
        // Sort the results.
        sort(begin(primes), end(primes), less<int>());

        // Copy the results to an IVector object. The IVector 
        // interface makes collections of data available to other 
        // Windows Runtime components.
        auto results = ref new Vector<int>();
        for (int prime : primes)
        {
            results->Append(prime);
        }
        return results;
    });
}

IAsyncOperationWithProgress<IVector<int>^, double>^ Primes::GetPrimesWithProgressAsync(int first, int last)
{
    return create_async([this, first, last](progress_reporter<double> reporter) -> IVector<int>^
    {
        // Ensure that the input values are in range.
        if (first < 0 || last < 0)
        {
            throw ref new InvalidArgumentException();
        }
        // Perform the computation in parallel.
        concurrent_vector<int> primes;
        long operation = 0;
        long range = last - first + 1;
        double lastPercent = 0.0;
        parallel_for(first, last + 1, [&primes, &operation, range, &lastPercent, reporter](int n)
        {
            // Report progress message.
            double progress = 100.0 * (++operation) / range;
            if (progress >= lastPercent)
            {
                reporter.report(progress);
                lastPercent += 1.0;
            }

            // If the value is prime, add it to the local vector. 
            if (is_prime(n))
            {
                primes.push_back(n);
            }
        });
        reporter.report(100.0);

        // Sort the results.
        sort(begin(primes), end(primes), less<int>());

        // Copy the results to an IVector object. The IVector 
        // interface makes collections of data available to other 
        // Windows Runtime components.
        auto results = ref new Vector<int>();
        for (int prime : primes)
        {
            results->Append(prime);
        }
        return results;
    });
}

各メソッドは最初に検証を実行して、入力パラメーターが負数でないことを確認します。 入力値が負数の場合、メソッドは Platform::InvalidArgumentExceptionをスローします。 エラー処理は、このセクションで後述します。

UWP アプリからこれらのメソッドを使用するには、Visual C# の [新しいアプリケーション (XAML)] テンプレートを使用して、Visual Studio ソリューションに 2 つ目のプロジェクトを追加します。 この例では、プロジェクトの名前を Primesとします。 次に、 Primes プロジェクトから PrimesLibrary プロジェクトへの参照を追加します。

MainPage.xaml に次のコードを追加します。 このコードは C++ コンポーネントを呼び出して結果を表示する UI を定義します。

<Page
    x:Class="Primes.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:Primes"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="300"/>
            <ColumnDefinition Width="300"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="125"/>
            <RowDefinition Height="125"/>
            <RowDefinition Height="125"/>
        </Grid.RowDefinitions>

        <StackPanel Grid.Column="0" Grid.Row="0">
            <Button Name="b1" Click="computePrimes">Compute Primes</Button>
            <TextBlock Name="tb1"></TextBlock>
        </StackPanel>

        <StackPanel Grid.Column="1" Grid.Row="0">
            <Button Name="b2" Click="computePrimesWithProgress">Compute Primes with Progress</Button>
            <ProgressBar Name="pb1" HorizontalAlignment="Left" Width="100"></ProgressBar>
            <TextBlock Name="tb2"></TextBlock>
        </StackPanel>

        <StackPanel Grid.Column="0" Grid.Row="1">
            <Button Name="b3" Click="getPrimes">Get Primes</Button>
            <TextBlock Name="tb3"></TextBlock>
        </StackPanel>

        <StackPanel Grid.Column="1" Grid.Row="1">
            <Button Name="b4" Click="getPrimesWithProgress">Get Primes with Progress</Button>
            <ProgressBar Name="pb4"  HorizontalAlignment="Left" Width="100"></ProgressBar>
            <TextBlock Name="tb4"></TextBlock>
        </StackPanel>

        <StackPanel Grid.Column="0" Grid.Row="2">
            <Button Name="b5" Click="getPrimesHandleErrors">Get Primes and Handle Errors</Button>
            <ProgressBar Name="pb5"  HorizontalAlignment="Left" Width="100"></ProgressBar>
            <TextBlock Name="tb5"></TextBlock>
        </StackPanel>

        <StackPanel Grid.Column="1" Grid.Row="2">
            <Button Name="b6" Click="getPrimesCancellation">Get Primes with Cancellation</Button>
            <Button Name="cancelButton" Click="cancelGetPrimes" IsEnabled="false">Cancel</Button>
            <ProgressBar Name="pb6"  HorizontalAlignment="Left" Width="100"></ProgressBar>
            <TextBlock Name="tb6"></TextBlock>
        </StackPanel>
    </Grid>
</Page>

MainPage.xaml で、次のコードを MainPage クラスに追加します。 このコードは Primes オブジェクトとボタンのイベント ハンドラーを定義します。

private PrimesLibrary.Primes primesLib = new PrimesLibrary.Primes();

private async void computePrimes(object sender, RoutedEventArgs e)
{
    b1.IsEnabled = false;
    tb1.Text = "Working...";

    var asyncAction = primesLib.ComputePrimesAsync(0, 100000);

    await asyncAction;

    tb1.Text = "Done";
    b1.IsEnabled = true;
}

private async void computePrimesWithProgress(object sender, RoutedEventArgs e)
{
    b2.IsEnabled = false;
    tb2.Text = "Working...";

    var asyncAction = primesLib.ComputePrimesWithProgressAsync(0, 100000);
    asyncAction.Progress = new AsyncActionProgressHandler<double>((action, progress) =>
    {
        pb1.Value = progress;
    });

    await asyncAction;

    tb2.Text = "Done";
    b2.IsEnabled = true;
}

private async void getPrimes(object sender, RoutedEventArgs e)
{
    b3.IsEnabled = false;
    tb3.Text = "Working...";

    var asyncOperation = primesLib.GetPrimesAsync(0, 100000);

    await asyncOperation;

    tb3.Text = "Found " + asyncOperation.GetResults().Count + " primes";
    b3.IsEnabled = true;
}

private async void getPrimesWithProgress(object sender, RoutedEventArgs e)
{
    b4.IsEnabled = false;
    tb4.Text = "Working...";

    var asyncOperation = primesLib.GetPrimesWithProgressAsync(0, 100000);
    asyncOperation.Progress = new AsyncOperationProgressHandler<IList<int>, double>((operation, progress) =>
    {
        pb4.Value = progress;
    });

    await asyncOperation;

    tb4.Text = "Found " + asyncOperation.GetResults().Count + " primes";
    b4.IsEnabled = true;
}

private async void getPrimesHandleErrors(object sender, RoutedEventArgs e)
{
    b5.IsEnabled = false;
    tb5.Text = "Working...";

    var asyncOperation = primesLib.GetPrimesWithProgressAsync(-1000, 100000);
    asyncOperation.Progress = new AsyncOperationProgressHandler<IList<int>, double>((operation, progress) =>
    {
        pb5.Value = progress;
    });

    try
    {
        await asyncOperation;
        tb5.Text = "Found " + asyncOperation.GetResults().Count + " primes";
    }
    catch (ArgumentException ex)
    {
        tb5.Text = "ERROR: " + ex.Message;
    }

    b5.IsEnabled = true;
}

private IAsyncOperationWithProgress<IList<int>, double> asyncCancelableOperation;

private async void getPrimesCancellation(object sender, RoutedEventArgs e)
{
    b6.IsEnabled = false;
    cancelButton.IsEnabled = true;
    tb6.Text = "Working...";

    asyncCancelableOperation = primesLib.GetPrimesWithProgressAsync(0, 200000);
    asyncCancelableOperation.Progress = new AsyncOperationProgressHandler<IList<int>, double>((operation, progress) =>
    {
        pb6.Value = progress;
    });

    try
    {
        await asyncCancelableOperation;
        tb6.Text = "Found " + asyncCancelableOperation.GetResults().Count + " primes";
    }
    catch (System.Threading.Tasks.TaskCanceledException)
    {
        tb6.Text = "Operation canceled";
    }

    b6.IsEnabled = true;
    cancelButton.IsEnabled = false;
}

private void cancelGetPrimes(object sender, RoutedEventArgs e)
{
    cancelButton.IsEnabled = false;
    asyncCancelableOperation.Cancel();
}

これらのメソッドは asyncawait のキーワードを使用して、非同期操作が完了した後で UI を更新します。 UWP アプリでの非同期コーディングについては、「スレッド化と非同期プログラミング」を参照してください。

getPrimesCancellation および cancelGetPrimes メソッドは連携して、ユーザーが操作の取り消しをできるようにします。 ユーザーが [キャンセル] ボタンを選択すると、cancelGetPrimes メソッドは IAsyncOperationWithProgress<TResult, TProgress>::Cancel を呼び出して操作を取り消します。 基になる非同期操作を管理する同時実行ランタイムは、Windows ランタイムがキャッチする内部例外をスローして、取り消し処理が完了したことを伝えます。 取り消しモデルの詳細については、取り消し処理に関する記事を参照してください。

重要

PPL が、操作を取り消したことを Windows ランタイムに正しく報告できるようにするために、この内部例外はキャッチしないでください。 これは、すべての例外 (catch (...)) をキャッチしないことを意味します。 すべての例外をキャッチする必要がある場合は、例外を再スローして、Windows ランタイムが取り消し操作を完了できるようにします。

次の図は、各オプションが選択された後の Primes アプリケーションを示しています。

Windows Runtime Primes app.

create_async を使用して、他の言語で使用できる非同期タスクを作成する例については、「Bing Maps Trip Optimizer での C++ の使用例」を参照してください。

実行スレッドを制御する

Windows ランタイムでは、COM スレッド モデルを使用します。 このモデルでは、オブジェクトは、同期を扱う方法によって、異なるアパートメント内でホストされます。 スレッド セーフなオブジェクトは、マルチスレッド アパートメント (MTA) でホストされます。 1 つのスレッドによりアクセスされる必要があるオブジェクトは、シングルスレッド アパートメント (STA) でホストされます。

UI があるアプリケーションでは、ASTA (アプリケーション STA) スレッドはウィンドウ メッセージをポンプする必要があり、STA によりホストされた UI コントロールを更新できるプロセスでの唯一のスレッドです。 これにより次の 2 つの結果を生じます。 最初に、アプリケーションの応答性を保つためには、すべての CPU 負荷の高い操作および I/O 操作は ASTA のスレッドで実行しないようにします。 第 2 に、バックグラウンド スレッドからの結果は、UI を更新する ASTA にマーシャリングされる必要があります。 C++ UWP アプリでは、MainPage と他の XAML ページは、すべて ATSA で実行されます。 したがって、ASTA で宣言されるタスクの継続は、既定ではその場で実行されるため、継続の本体でコントロールを直接更新できます。 ただし、タスクが別のタスクの入れ子になっている場合、入れ子のタスクのすべての継続は MTA で実行されます。 したがって、継続が実行されるコンテキストを明示的に指定するかどうかを検討する必要があります。

IAsyncOperation<TResult>など、非同期操作から作成されたタスクは、特別な意味を使用するので、スレッド処理の詳細に注意する必要はありません。 操作はバックグラウンド スレッドで実行できますが (またはスレッドにまったくサポートされない場合もあります)、継続は既定では継続の操作を開始したアパートメントでの実行を保証されています (つまり task::thenを呼び出したアパートメントから実行されます)。 concurrency::task_continuation_context クラスを使用して、継続の実行コンテキストを制御できます。 これらの静的ヘルパー メソッドを使用して task_continuation_context オブジェクトを作成します。

task_continuation_context オブジェクトを task::then メソッドに渡して、継続の実行コンテキストを明示的に制御できます。またはタスクを別のアパートメントに渡してから task::then メソッドを呼び出して、暗黙的に実行コンテキストを制御できます。

重要

UWP アプリのメイン UI スレッドは STA で実行されるため、STA で作成した継続は既定では STA で実行されます。 したがって、MTA に作成した継続は MTA 内で実行されます。

次のセクションでは、ディスクからファイルを読み込み、そのファイルで最もよく使われている単語を検索し、結果を UI に表示するアプリケーションを示します。 UI を更新する最後の操作は、UI スレッドで発生します。

重要

この動作は UWP アプリに固有です。 デスクトップ アプリケーションでは、継続が実行される場所を制御できません。 その代わりに、各継続が実行されるワーカー スレッドをスケジューラが選択します。

重要

STA で実行される継続の本体で concurrency::task::wait を呼び出さないでください。 そうしないと、このメソッドが現在のスレッドをブロックして、アプリケーションが応答しなくなる場合があるため、ランタイムは concurrency::invalid_operation をスローします。 ただし、タスク ベースの継続で継続元タスクの結果を受け取るために concurrency::task::get のメソッドを呼び出すことができます。

例: C++ と XAML を使用した Windows ランタイム アプリでの実行の制御

ディスクからファイルを読み込み、そのファイルで最もよく使われている単語を検索し、結果を UI に表示する C++ XAML アプリケーションを考えてみます。 このアプリを作成するには、まず、Visual Studio で [空のアプリ (ユニバーサル Windows)] プロジェクトを作成し、CommonWords という名前を付けます。 アプリケーション マニフェストで、 [ドキュメント ライブラリ] の機能を指定して、アプリケーションがドキュメント フォルダーにアクセスできるようにします。 また、アプリケーション マニフェストの宣言セクションにテキスト (.txt) ファイルの種類を追加します。 アプリの機能と宣言の詳細については、「Windows アプリのパッケージ化、展開、クエリ」を参照してください。

Grid 要素と ProgressRing 要素を含めるように、MainPage.xaml の TextBlock 要素を更新します。 ProgressRing は操作が進行中であることを示し、 TextBlock は計算の結果を示します。

<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
    <ProgressRing x:Name="Progress"/>
    <TextBlock x:Name="Results" FontSize="16"/>
</Grid>

次の #include ステートメントを pch.h に追加します。

#include <sstream>
#include <ppltasks.h>
#include <concurrent_unordered_map.h>

次のメソッド宣言を MainPage クラス (MainPage.h) に追加します。

private:
    // Splits the provided text string into individual words.
    concurrency::task<std::vector<std::wstring>> MakeWordList(Platform::String^ text);

    // Finds the most common words that are at least the provided minimum length.
    concurrency::task<std::vector<std::pair<std::wstring, size_t>>> FindCommonWords(const std::vector<std::wstring>& words, size_t min_length, size_t count);

    // Shows the most common words on the UI.
    void ShowResults(const std::vector<std::pair<std::wstring, size_t>>& commonWords);

次の using ステートメントを MainPage.cpp に追加します。

using namespace concurrency;
using namespace std;
using namespace Windows::Storage;
using namespace Windows::Storage::Streams;

MainPage.cpp で、 MainPage::MakeWordListMainPage::FindCommonWords、および MainPage::ShowResults のメソッドを実装します。 MainPage::MakeWordListMainPage::FindCommonWords は計算量が非常に多い演算操作を行います。 MainPage::ShowResults メソッドは演算の結果を UI に表示します。

// Splits the provided text string into individual words.
task<vector<wstring>> MainPage::MakeWordList(String^ text)
{
    return create_task([text]() -> vector<wstring>
    {
        vector<wstring> words;

        // Add continuous sequences of alphanumeric characters to the string vector.
        wstring current_word;
        for (wchar_t ch : text)
        {
            if (!iswalnum(ch))
            {
                if (current_word.length() > 0)
                {
                    words.push_back(current_word);
                    current_word.clear();
                }
            }
            else
            {
                current_word += ch;
            }
        }

        return words;
    });
}

// Finds the most common words that are at least the provided minimum length.
task<vector<pair<wstring, size_t>>> MainPage::FindCommonWords(const vector<wstring>& words, size_t min_length, size_t count)
{
    return create_task([words, min_length, count]() -> vector<pair<wstring, size_t>>
    {
        typedef pair<wstring, size_t> pair;

        // Counts the occurrences of each word.
        concurrent_unordered_map<wstring, size_t> counts;

        parallel_for_each(begin(words), end(words), [&counts, min_length](const wstring& word)
        {
            // Increment the count of words that are at least the minimum length. 
            if (word.length() >= min_length)
            {
                // Increment the count.
                InterlockedIncrement(&counts[word]);
            }
        });

        // Copy the contents of the map to a vector and sort the vector by the number of occurrences of each word.
        vector<pair> wordvector;
        copy(begin(counts), end(counts), back_inserter(wordvector));

        sort(begin(wordvector), end(wordvector), [](const pair& x, const pair& y)
        {
            return x.second > y.second;
        });

        size_t size = min(wordvector.size(), count);
        wordvector.erase(begin(wordvector) + size, end(wordvector));

        return wordvector;
    });
}

// Shows the most common words on the UI. 
void MainPage::ShowResults(const vector<pair<wstring, size_t>>& commonWords)
{
    wstringstream ss;
    ss << "The most common words that have five or more letters are:";
    for (auto commonWord : commonWords)
    {
        ss << endl << commonWord.first << L" (" << commonWord.second << L')';
    }

    // Update the UI.
    Results->Text = ref new String(ss.str().c_str());
}

MainPage コンストラクターを変更し、Homer による著書 The Iliad でよく使われている単語を UI に表示する継続タスクのチェーンを作成します。 最初の 2 つの継続タスクは、テキストを個々の単語に分割し、よく使われている単語を検索します。これには時間がかかるため、バックグラウンドで実行されるように明示的に設定されています。 最後の継続タスクは UI を更新します。これは継続コンテキストを指定していないので、アパートメント スレッドの規則に従います。

MainPage::MainPage()
{
    InitializeComponent();

    // To run this example, save the contents of http://www.gutenberg.org/files/6130/6130-0.txt to your Documents folder.
    // Name the file "The Iliad.txt" and save it under UTF-8 encoding.

    // Enable the progress ring.
    Progress->IsActive = true;

    // Find the most common words in the book "The Iliad".

    // Get the file.
    create_task(KnownFolders::DocumentsLibrary->GetFileAsync("The Iliad.txt")).then([](StorageFile^ file)
    {
        // Read the file text.
        return FileIO::ReadTextAsync(file, UnicodeEncoding::Utf8);

        // By default, all continuations from a Windows Runtime async operation run on the 
        // thread that calls task.then. Specify use_arbitrary to run this continuation 
        // on a background thread.
    }, task_continuation_context::use_arbitrary()).then([this](String^ file)
    {
        // Create a word list from the text.
        return MakeWordList(file);

        // By default, all continuations from a Windows Runtime async operation run on the 
        // thread that calls task.then. Specify use_arbitrary to run this continuation 
        // on a background thread.
    }, task_continuation_context::use_arbitrary()).then([this](vector<wstring> words)
    {
        // Find the most common words.
        return FindCommonWords(words, 5, 9);

        // By default, all continuations from a Windows Runtime async operation run on the 
        // thread that calls task.then. Specify use_arbitrary to run this continuation 
        // on a background thread.
    }, task_continuation_context::use_arbitrary()).then([this](vector<pair<wstring, size_t>> commonWords)
    {
        // Stop the progress ring.
        Progress->IsActive = false;

        // Show the results.
        ShowResults(commonWords);

        // We don't specify a continuation context here because we want the continuation 
        // to run on the STA thread.
    });
}

Note

この例では、実行コンテキストを指定する方法と、継続のチェーンを構成する方法を示します。 非同期操作から作成されたタスクは、既定では task::thenを呼び出したアパートメントで継続を実行することを思い出してください。 したがって、この例では task_continuation_context::use_arbitrary を使用して、UI が含まれていない操作をバックグラウンド スレッドで実行するように指定しています。

次の図は CommonWords アプリケーションの結果を示しています。

Windows Runtime CommonWords app.

この例では、create_async をサポートする task オブジェクトが暗黙的なキャンセル トークンを使用しているため、取り消しをサポートできます。 タスクが協調的に取り消しに応答する必要がある場合には、 cancellation_token オブジェクトを受け取るように処理関数を定義します。 PPL での取り消し処理の詳細については、「 Cancellation in the PPL」を参照してください。

関連項目

コンカレンシー ランタイム