次の方法で共有


PPL における取り消し処理

このドキュメントは、並列パターン ライブラリ (PPL) のキャンセル処理の役割、並列処理を取り消す方法、および並列処理の取り消し状態を確認する方法を示しています。

[!メモ]

ランタイムは例外処理を使用して取り消し処理を実装します。これらの例外をコードでキャッチまたは処理しないでください。さらに、タスクの関数本体では例外セーフなコードを作成することをお勧めします。たとえば、Resource Acquisition Is Initialization (RAII) パターンを使用すると、例外がタスクの本体でスローされたときに、リソースを正しく処理することができます。取り消し可能なタスクで RAII パターンを使用してリソースをクリーンアップする完全な例については、「チュートリアル: ユーザー インターフェイス スレッドからの処理の除去」を参照してください。

キー

  • キャンセルが協調的で、キャンセルとキャンセルに応答するタスクを必要とするコード間で調整があります。

  • 可能な場合は、処理を取り消す使用キャンセル トークン。concurrency::cancellation_token のクラスは、キャンセル トークンを定義します。

  • キャンセル トークンを使用すると、取り消し処理と concurrency::is_task_cancellation_requestedconcurrency::cancel_current_task 関数をキャンセルに応答できるようにするために concurrency::cancellation_token_source::cancel のメソッドを使用します。

  • すぐに取り消し処理は行われません。タスクまたはタスク グループが取り消される新たに起動されませんが、アクティブな作業はのチェックし、キャンセルに応答する必要があります。

  • 値ベースの継続は、継続元タスクのキャンセル トークンを継承します。タスク ベースの継続は、継続元タスク トークンを継承しません。

  • cancellation_token のオブジェクトを受け取るには、キャンセル可能な操作でされないようにコンストラクターを呼び出すか、または機能するとき concurrency::cancellation_token::none のメソッドを使用します。また concurrency::task のコンストラクターまたは concurrency::create_task の関数にキャンセル トークンを渡さない場合、そのタスクは、キャンセル可能なではありません。

このドキュメント

  • 並列処理ツリー

  • 並列タスクの取り消し

    • 並列処理を取り消すキャンセル トークンを使用する

    • cancel メソッドを使用した並列処理の取り消し

    • 例外を使用した並列処理の取り消し

  • 並列アルゴリズムの取り消し

  • 取り消し処理が適さないケース

並列処理ツリー

PPL では、タスクおよびタスク グループを粒度の細かいタスクおよび計算を管理するために使用します。タスク グループを入れ子にすることで、並列処理のツリーを形成できます。並列処理ツリーの図を次に示します。この図では、tg1tg2 はタスク グループを表します; t1t2t3t4t5 では、タスク グループが実行する作業を表します。

並列処理ツリー

前の図のツリーを作成するために必要なコード例を次に示します。この例では、tg1tg2 はオブジェクトです concurrency::structured_task_group ; t1t2t3t4t5concurrency::task_handle のオブジェクトです。

// task-tree.cpp
// compile with: /c /EHsc
#include <ppl.h>
#include <sstream>
#include <iostream>
#include <sstream>

using namespace concurrency;
using namespace std;

void create_task_tree()
{   
   // Create a task group that serves as the root of the tree.
   structured_task_group tg1;

   // Create a task that contains a nested task group.
   auto t1 = make_task([&] {
      structured_task_group tg2;

      // Create a child task.
      auto t4 = make_task([&] {
         // TODO: Perform work here.
      });

      // Create a child task.
      auto t5 = make_task([&] {
         // TODO: Perform work here.
      });

      // Run the child tasks and wait for them to finish.
      tg2.run(t4);
      tg2.run(t5);
      tg2.wait();
   });

   // Create a child task.
   auto t2 = make_task([&] {
      // TODO: Perform work here.
   });

   // Create a child task.
   auto t3 = make_task([&] {
      // TODO: Perform work here.
   });

   // Run the child tasks and wait for them to finish.
   tg1.run(t1);
   tg1.run(t2);
   tg1.run(t3);
   tg1.wait();   
}

また、同様の処理ツリーを作成するに concurrency::task_group のクラスを使用できます。concurrency::task のクラスは、処理ツリーの概念をサポートしています。ただし、task のツリーは、依存関係ツリーです。task のツリーで、将来の作業は、現在の作業の完了後にします。タスク グループのツリーでは、内部処理は外側の作業の前に実行されます。タスクとタスク グループの違いに関する詳細については、タスクの並列化 (同時実行ランタイム)を参照してください。

[入力]上

並列タスクの取り消し

並列処理を取り消す方法は複数あります。推奨される方法は、キャンセル トークンを使用することです。タスク グループも concurrency::task_group::cancel のメソッドと concurrency::structured_task_group::cancel のメソッドをサポートします。最終的な方法は、タスクの処理関数の本体で例外をスローすることです。メソッドが選択し、取り消し処理はすぐに発生しないことを理解する問題はありません。タスクまたはタスク グループが取り消される新たに起動されませんが、アクティブな作業はのチェックし、キャンセルに応答する必要があります。

並列タスクを取り消すそのほかの例については、チュートリアル: タスクおよび XML HTTP 要求を使用した接続 (IXHR2)方法: キャンセル処理を使用して並列ループを中断する方法: 例外処理を使用して並列ループを中断するを参照してください。

Dd984117.collapse_all(ja-jp,VS.110).gif並列処理を取り消すキャンセル トークンを使用する

tasktask_groupstructured_task_group のクラスは、キャンセル トークンを使用したキャンセルをサポートします。PPL では concurrency::cancellation_token_sourceconcurrency::cancellation_token のクラスをこのオプションを定義します。処理を取り消すには、キャンセル トークンを使用すると、ランタイムはそのトークンにサブスクライブするな更新操作を開始しません。使用できる場合は。既にアクティブ キャンセル トークンを監視および停止するためです。

取り消しを開始するには、concurrency::cancellation_token_source::cancel のメソッドを呼び出します。これらの方法でキャンセルに応答します:

  • task のオブジェクト、concurrency::is_task_cancellation_requestedconcurrency::cancel_current_task 関数を使用します。cancel_current_task は、現在のタスクとの値ベースの継続取り消します。(これは、タスクまたは継続に関連付けられたキャンセル トークンは取り消されません。)

  • タスク グループと並列アルゴリズムの場合、この関数がを返す場合 trueキャンセルを検出し、タスクの本体からできるだけ早く戻るために concurrency::is_current_task_group_canceling 関数を使用します。(タスク グループの cancel_current_task を呼び出さないでください)。

次の例では、タスクの取り消し処理の最初の基本的なパターンを示しています。タスクの本体では、ループの中にキャンセルがときどき確認します。

// task-basic-cancellation.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>
#include <sstream>

using namespace concurrency;
using namespace std;

bool do_work()
{
    // Simulate work.
    wcout << L"Performing work..." << endl;
    wait(250);
    return true;
}

int wmain()
{
    cancellation_token_source cts;
    auto token = cts.get_token();

    wcout << L"Creating task..." << endl;

    // Create a task that performs work until it is canceled.
    auto t = create_task([]
    {
        bool moreToDo = true;
        while (moreToDo)
        {
            // Check for cancellation.
            if (is_task_cancellation_requested())
            {
                // TODO: Perform any necessary cleanup here...

                // Cancel the current task.
                cancel_current_task();
            }
            else 
            {
                // Perform work.
                moreToDo = do_work();
            }
        }
    }, token);

    // Wait for one second and then cancel the task.
    wait(1000);

    wcout << L"Canceling task..." << endl;
    cts.cancel();

    // Wait for the task to cancel.
    wcout << L"Waiting for task to complete..." << endl;
    t.wait();

    wcout << L"Done." << endl;
}

/* Sample output:
    Creating task...
    Performing work...
    Performing work...
    Performing work...
    Performing work...
    Canceling task...
    Waiting for task to complete...
    Done.
*/

cancel_current_task 関数は; したがって、明示的に現在のループまたは関数から返される必要はありません。

ヒントヒント

また、is_task_cancellation_requestedcancel_current_taskの代わりに concurrency::interruption_point 関数を呼び出すことができます。

キャンセルにため、Canceled 状態に遷移するタスクと cancel_current_task を呼び出すことが重要です。cancel_current_taskを呼び出す代わりにすぐに制御を返すと、操作の完了状態への遷移と値ベースの継続が実行されます。

Caution メモ注意

は、コードから task_canceled をスローしないでください。代わりに使用 cancel_current_task

取り消された状態のタスクが終了 concurrency::task::get、のメソッド concurrency::task_canceledをスローした場合。(逆に、concurrency::task::waittask_status::canceled を返し、は)。次の例では、タスク ベースの継続のこの動作を示します。タスク ベースの継続は、継続元タスクが取り消された場合でも、常に呼び出されます。

// task-canceled.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
    auto t1 = create_task([]() -> int
    {
        // Cancel the task.
        cancel_current_task();
    });

    // Create a continuation that retrieves the value from the previous.
    auto t2 = t1.then([](task<int> t)
    {
        try
        {
            int n = t.get();
            wcout << L"The previous task returned " << n << L'.' << endl;
        }
        catch (const task_canceled& e)
        {
            wcout << L"The previous task was canceled." << endl;
        }
    });

    // Wait for all tasks to complete.
    t2.wait();
}
/* Output:
    The previous task was canceled.
*/

明示的なトークンで作成されていない値ベースの継続で継続元タスク トークンを継承するため、継続は、継続元タスクがまだ実行している場合でも取り消された状態になります。したがって、取り消し処理が継続に広がらなかった後、継続元タスクがスローした例外によりもサポートします。取り消し処理が継続元タスクの状態を常にオーバーライドします。次の例は前の例に似ていますが、値ベースの継続の動作を示しています。

auto t1 = create_task([]() -> int
{
    // Cancel the task.
    cancel_current_task();
});

// Create a continuation that retrieves the value from the previous.
auto t2 = t1.then([](int n)
{
    wcout << L"The previous task returned " << n << L'.' << endl;
});

try
{
    // Wait for all tasks to complete.
    t2.get();
}
catch (const task_canceled& e)
{
    wcout << L"The task was canceled." << endl;
}
/* Output:
    The task was canceled.
*/
Caution メモ注意

task のコンストラクターまたは concurrency::create_task の関数にキャンセル トークンを渡す、そのタスクは、キャンセル可能なではありません。また、別のタスクの本体で作成された入れ子のタスク (つまり、タスク) のコンストラクターに同じキャンセル トークンをすべてのタスクを同時にキャンセル渡す必要があります。

キャンセル トークンが取り消されると、任意のコードを実行する場合もあります。たとえば、操作をキャンセルするために、ユーザーがユーザー インターフェイスの [キャンセル] のボタンを選択した場合、ユーザーが別の操作を開始するまでそのボタンを無効にすることもできます。キャンセル トークンをキャンセルすると次の例にコールバック関数を登録するに concurrency::cancellation_token::register_callback のメソッドを使用して実行する方法を示します。

// task-cancellation-callback.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
    cancellation_token_source cts;
    auto token = cts.get_token();

    // An event that is set in the cancellation callback.
    event e;

    cancellation_token_registration cookie;
    cookie = token.register_callback([&e, token, &cookie]()
    {
        wcout << L"In cancellation callback..." << endl;
        e.set();

        // Although not required, demonstrate how to unregister 
        // the callback.
        token.deregister_callback(cookie);
    });

    wcout << L"Creating task..." << endl;

    // Create a task that waits to be canceled.
    auto t = create_task([&e]
    {
        e.wait();
    }, token);

    // Cancel the task.
    wcout << L"Canceling task..." << endl;
    cts.cancel();

    // Wait for the task to cancel.
    t.wait();

    wcout << L"Done." << endl;
}
/* Sample output:
    Creating task...
    Canceling task...
    In cancellation callback...
    Done.
*/

ドキュメント タスクの並列化 (同時実行ランタイム) 値はベースとタスク ベースの継続の違いを示しています。継続タスクへの cancellation_token のオブジェクトを提供する、継続元タスクのキャンセル トークンを次のように継承します:

  • 値ベースの継続は、継続元タスクのキャンセル トークンを常に継承します。

  • タスク ベースの継続は、継続元タスクのキャンセル トークンを継承しません。タスク ベースの継続をキャンセルする唯一の方法は、明示的にキャンセル トークンを渡すことです。

これらの動作は、使用されなくなったタスク (つまり、が例外をスローする) 1 影響しません。この場合、値ベースの継続は取り消されます; タスク ベースの継続は取り消されません。

Caution メモ注意

別のタスク (つまり、入れ子のタスク) に作成されたタスクは親タスクのキャンセル トークンを継承しません。値ベースの継続のみ継続元タスクのキャンセル トークンを継承します。

ヒントヒント

cancellation_token のオブジェクトを受け取るにキャンセル可能な操作でされないようにコンストラクターを呼び出すか、または機能するとき concurrency::cancellation_token::none のメソッドを使用します。

また task_group または structured_task_group オブジェクトのコンストラクターにキャンセル トークンを提供できます。このの重要な側面は、子タスク グループがこのキャンセル トークンを継承します。parallel_forを呼び出すの実装に concurrency::run_with_cancellation_token 関数を使用してこの概念の例については、このドキュメントで後で 並列アルゴリズムを取り消すこと を参照してください。

[入力]上

Dd984117.collapse_all(ja-jp,VS.110).gifキャンセル トークンやタスクの構成

concurrency::when_allconcurrency::when_any 関数は、一般的なパターンを実装するいくつかのタスクを構成することができます。このセクションでは、これらの関数が [キャンセル トークンを使用する方法について説明します。

どちらにキャンセル トークンを when_allwhen_any 関数提供する場合、そのキャンセル トークンを取り消すとき、または取り消された状態に関係するタスクの最後の 1 つがまたは例外をスローするときにその関数のキャンセル。

when_all 関数は、キャンセル トークンを提供しないときに操作全体を構成する各タスクからキャンセル トークンを継承します。when_all から返されるタスクは、これらのトークンが取り消されたタスクがまだ開始されておらず、または実行されていない参加要素の少なくとも 1 つがと、取り消され。同様の動作は、タスクの 1 つが例外 – をその例外に when_all からすぐにキャンセルを返すタスク スローされます。

ランタイムは、タスクが完了したとき when_any 関数から返されるタスクのキャンセル トークンを選択します。参加要素のタスクのいずれも完了状態で終わらないし、一つまたは複数のタスクが例外をスロー when_any とトークンを完了するには、スローしたタスクの 1 つが、最後のタスクのトークンとして選択されますが選択されます。複数のタスクが完了状態に終了した場合、タスクの完了ステータス when_any の端から返されるタスク。トークンの終了時にとして when_any から返されるタスク取り消されない完了したタスクを選択は、ランタイムは他のタスクの実行が後で完了した場合でも、取り消されません。

[入力]上

Dd984117.collapse_all(ja-jp,VS.110).gifcancel メソッドを使用した並列処理の取り消し

concurrency::task_group::cancelconcurrency::structured_task_group::cancel のメソッドにタスク グループが取り消された状態を設定します。cancel を呼び出すと、それ以降はタスク グループでタスクが開始されなくなります。cancel メソッドは、複数の子タスクから呼び出すことができます。取り消されたタスクにより concurrency::task_group::waitconcurrency::structured_task_group::wait のメソッドは concurrency::canceledを返します。

タスク グループが取り消されると、各子タスクからのランタイムの呼び出しによって割り込みポイントが発生する場合があります。このような場合、ランタイムは内部的な例外をスローおよびキャッチして、実行中のタスクを取り消します。同時実行ランタイムでは、特定の割り込みポイントが定義されていません。割り込みポイントは、ランタイムに対する任意の呼び出しで発生します。ランタイムは、取り消し処理を実行するために、自身がスローした例外を処理する必要があります。このため、タスクの本体で不明な例外を処理しないでください。

子タスクが時間のかかる処理を実行しており、ランタイムの呼び出しを行わない場合、子タスクは定期的に取り消し状態をチェックして、適切なタイミングで終了する必要があります。処理の取り消し状態を判定する方法の 1 つを次の例に示します。タスク t4 は、エラーの発生時に親タスク グループを取り消します。タスク t5 は、structured_task_group::is_canceling メソッドを定期的に呼び出して、取り消し状態をチェックします。親タスク グループが取り消されると、タスク t5 はメッセージを表示して終了します。

structured_task_group tg2;

// Create a child task.
auto t4 = make_task([&] {
   // Perform work in a loop.
   for (int i = 0; i < 1000; ++i)
   {
      // Call a function to perform work.
      // If the work function fails, cancel the parent task
      // and break from the loop.
      bool succeeded = work(i);
      if (!succeeded)
      {
         tg2.cancel();
         break;
      }
   }
});

// Create a child task.
auto t5 = make_task([&] {
   // Perform work in a loop.
   for (int i = 0; i < 1000; ++i)
   {
      // To reduce overhead, occasionally check for 
      // cancelation.
      if ((i%100) == 0)
      {
         if (tg2.is_canceling())
         {
            wcout << L"The task was canceled." << endl;
            break;
         }
      }

      // TODO: Perform work here.
   }
});

// Run the child tasks and wait for them to finish.
tg2.run(t4);
tg2.run(t5);
tg2.wait();

この例では、タスク ループが 100 回繰り返されるたびに取り消し状態がチェックされます。取り消し状態をチェックする頻度は、タスクで実行される処理の量と、取り消し処理にタスクがどの程度すばやく応答する必要があるのかによって決まります。

親タスク グループ オブジェクトにアクセスできない場合は、親タスク グループが取り消されるかどうかを確認するに concurrency::is_current_task_group_canceling 関数を呼び出します。

cancel メソッドは、子タスクにのみ影響を及ぼします。たとえば、並列処理ツリーの図のタスク グループ tg1 を取り消すと、ツリー内のすべてのタスク (t1t2t3t4、および t5) が取り消されます。入れ子になったタスク グループ tg2 を取り消すと、タスク t4 および t5 のみ影響を受けます。

cancel メソッドを呼び出すと、子タスク グループもすべて取り消されます。しかし、取り消し処理は、並列処理ツリーにおけるタスク グループの親には影響しません。以降の例では、並列処理ツリーの図に基づいてこのことを示します。

最初の例では、タスク グループ tg2 の子であるタスク t4 の処理関数を作成します。この処理関数は、関数 work をループ内で呼び出します。work の呼び出しが失敗すると、タスクは親タスク グループを取り消します。これにより、タスク グループ tg2 が取り消された状態になりますが、タスク グループ tg1 は取り消されません。

auto t4 = make_task([&] {
   // Perform work in a loop.
   for (int i = 0; i < 1000; ++i)
   {
      // Call a function to perform work.
      // If the work function fails, cancel the parent task
      // and break from the loop.
      bool succeeded = work(i);
      if (!succeeded)
      {
         tg2.cancel();
         break;
      }
   }         
});

次の例は、最初の例と似ていますが、タスクがタスク グループ tg1 を取り消す点が異なっています。これにより、ツリー内のすべてのタスク (t1t2t3t4、および t5) が影響を受けます。

auto t4 = make_task([&] {
   // Perform work in a loop.
   for (int i = 0; i < 1000; ++i)
   {
      // Call a function to perform work.
      // If the work function fails, cancel all tasks in the tree.
      bool succeeded = work(i);
      if (!succeeded)
      {
         tg1.cancel();
         break;
      }
   }   
});

structured_task_group クラスはスレッドセーフではありません。したがって、子タスクが親 structured_task_group オブジェクトのメソッドを呼び出すと、未定義の動作が実行されます。この規則の例外は structured_task_group::cancelconcurrency::structured_task_group::is_canceling のメソッドです。子タスクが、キャンセルのチェックおよび親タスク グループを取り消すには、これらのメソッドを呼び出すことができます。

Caution メモ注意

task オブジェクトの子として実行するタスク グループ実行される処理を取り消すためにキャンセル トークンを使用できますが、タスク グループで実行 task のオブジェクトを取り消すために task_group::cancel または structured_task_group::cancel のメソッドを使用することはできません。

[入力]上

Dd984117.collapse_all(ja-jp,VS.110).gif例外を使用した並列処理の取り消し

キャンセル トークンを使用および cancel のメソッドは、並列処理ツリーを取り消す場合は、例外処理を行うよりも効率的です。キャンセル トークンと cancel のメソッドは、上の方法でタスクと子タスクをキャンセルします。反対に、例外処理では下位から順に処理されるため、例外が上位へ移動するたびに、子タスク グループを個別に取り消す必要があります。「同時実行ランタイムでの例外処理」では、同時実行ランタイムが例外を使用してエラーを通知するしくみについて説明しています。しかし、すべての例外がエラーの発生を示す訳ではありません。たとえば、検索アルゴリズムは結果を検出したときに関連するタスクを取り消すことがあります。しかし、前述したように、並列処理の取り消しを行う場合、例外処理は cancel メソッドを使用するよりも効率の点で劣ります。

Caution メモ注意

これは、だけが必要な場合だけを並列処理を取り消すために例外を使用することをお勧めします。キャンセル トークンおよびタスク グループの cancel のメソッドにより、あまりエラーが発生しやすくなります。

タスク グループに渡す処理関数の本体で例外をスローすると、ランタイムはその例外を保存して、タスク グループの終了を待機するコンテキストにその例外をマーシャリングします。cancel メソッドの場合と同様に、ランタイムはまだ開始されていないタスクを破棄し、新しいタスクを受け付けません。

次の 3 つ目の例は、2 つ目の例と似ていますが、タスク t4 が例外をスローすることでタスク グループ tg2 を取り消している点が異なります。この例では、try-catch ブロックを使用して、タスク グループ tg2 が子タスクの終了を待機しているときに取り消し状態をチェックします。最初の例と同様に、これによってタスク グループ tg2 が取り消された状態になりますが、タスク グループ tg1 は取り消されません。

structured_task_group tg2;

// Create a child task.      
auto t4 = make_task([&] {
   // Perform work in a loop.
   for (int i = 0; i < 1000; ++i)
   {
      // Call a function to perform work.
      // If the work function fails, throw an exception to 
      // cancel the parent task.
      bool succeeded = work(i);
      if (!succeeded)
      {
         throw exception("The task failed");
      }
   }         
});

// Create a child task.
auto t5 = make_task([&] {
   // TODO: Perform work here.
});

// Run the child tasks.
tg2.run(t4);
tg2.run(t5);

// Wait for the tasks to finish. The runtime marshals any exception
// that occurs to the call to wait.
try
{
   tg2.wait();
}
catch (const exception& e)
{
   wcout << e.what() << endl;
}

次の 4 つ目の例では、例外処理を使用して処理ツリー全体を取り消します。この例では、タスク グループ tg2 が子タスクの終了を待機しているときではなく、タスク グループ tg1 が子タスクの終了を待機しているときに、例外をキャッチします。2 番目の例と同様に、これによってツリー内の 2 つのタスク グループ tg1 および tg2 の両方が取り消された状態になります。

// Run the child tasks.
tg1.run(t1);
tg1.run(t2);
tg1.run(t3);   

// Wait for the tasks to finish. The runtime marshals any exception
// that occurs to the call to wait.
try
{
   tg1.wait();
}
catch (const exception& e)
{
   wcout << e.what() << endl;
}

task_group::wait メソッドと structured_task_group::wait メソッドは、子タスクが例外をスローしたときに実行されるため、これらのメソッドから戻り値を受け取ることはありません。

[入力]上

並列アルゴリズムの取り消し

PPL の並列アルゴリズム、タスク グループのたとえば、parallel_forのビルド。このため、これまで説明した方法のうちの多くを使用して、並列アルゴリズムを取り消すことができます。

以降の例では、並列アルゴリズムを取り消すためのいくつかの方法を示します。

次の例では parallel_for のアルゴリズムの呼び出しに run_with_cancellation_token 関数を使用します。run_with_cancellation_token の関数は、引数としてキャンセル トークンを取得し、指定された処理関数を同期的に呼び出します。並列アルゴリズムではタスクに組み込まれているため、親タスクのキャンセル トークンを継承します。したがって、parallel_for はキャンセルに応答できます。

// cancel-parallel-for.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>
#include <sstream>

using namespace concurrency;
using namespace std;

int wmain()
{
    // Call parallel_for in the context of a cancellation token.
    cancellation_token_source cts;
    run_with_cancellation_token([&cts]() 
    {
        // Print values to the console in parallel.
        parallel_for(0, 20, [&cts](int n)
        {
            // For demonstration, cancel the overall operation 
            // when n equals 11.
            if (n == 11)
            {
                cts.cancel();
            }
            // Otherwise, print the value.
            else
            {
                wstringstream ss;
                ss << n << endl;
                wcout << ss.str();
            }
        });
    }, cts.get_token());
}
/* Sample output:
    15
    16
    17
    10
    0
    18
    5
*/

次の例では parallel_for のアルゴリズムの呼び出しに concurrency::structured_task_group::run_and_wait のメソッドを使用します。structured_task_group::run_and_wait メソッドは、指定されたタスクの終了を待機します。structured_task_group オブジェクトによって、処理関数でタスクを取り消すことができるようになります。

// To enable cancelation, call parallel_for in a task group.
structured_task_group tg;

task_group_status status = tg.run_and_wait([&] {
   parallel_for(0, 100, [&](int i) {
      // Cancel the task when i is 50.
      if (i == 50)
      {
         tg.cancel();
      }
      else
      {
         // TODO: Perform work here.
      }
   });
});

// Print the task group status.
wcout << L"The task group status is: ";
switch (status)
{
case not_complete:
   wcout << L"not complete." << endl;
   break;
case completed:
   wcout << L"completed." << endl;
   break;
case canceled:
   wcout << L"canceled." << endl;
   break;
default:
   wcout << L"unknown." << endl;
   break;
}

この例を実行すると、次の出力が生成されます。

The task group status is: canceled.

例外処理を使用して parallel_for ループを取り消す例を次に示します。ランタイムは、例外を呼び出し元のコンテキストにマーシャリングします。

try
{
   parallel_for(0, 100, [&](int i) {
      // Throw an exception to cancel the task when i is 50.
      if (i == 50)
      {
         throw i;
      }
      else
      {
         // TODO: Perform work here.
      }
   });
}
catch (int n)
{
   wcout << L"Caught " << n << endl;
}

この例を実行すると、次の出力が生成されます。

Caught 50

ブール型のフラグを使用して parallel_for ループに取り消し処理を組み込む例を次に示します。この例では、cancel メソッドや例外処理を使用して一連のタスク全体を取り消している訳ではないため、すべてのタスクが実行されます。したがって、この方法では、取り消しメソッドを使用するよりも多くの計算オーバーヘッドが発生する場合があります。

// Create a Boolean flag to coordinate cancelation.
bool canceled = false;

parallel_for(0, 100, [&](int i) {
   // For illustration, set the flag to cancel the task when i is 50.
   if (i == 50)
   {
      canceled = true;
   }

   // Perform work if the task is not canceled.
   if (!canceled)
   {
      // TODO: Perform work here.
   }
});

どの取り消し方法にも、他の方法にはない利点があります。特定のニーズに合った方法を採用してください。

[入力]上

取り消し処理が適さないケース

取り消し処理は、関連するタスク グループの各メンバーが適時に終了できる場合には適しています。しかし、取り消し処理がアプリケーションに適さないケースもあります。たとえば、タスクの取り消しは他の処理と連携して行われるため、個別のタスクがブロックされている場合は、一連のタスク全体を取り消すことができなくなります。また、あるタスクがまだ開始されていないが、他の実行中のタスクのブロックを解除する場合、タスク グループが取り消されると、そのタスクは開始されません。これにより、アプリケーションでデッドロックが発生する場合があります。また、取り消し対象のタスクの子タスクで重要な操作 (リソースの解放など) が実行されるような状況でも、取り消し処理の使用はふさわしくありません。親タスクを取り消すと一連のタスクがすべて取り消されるため、その操作は実行されなくなります。この点を示す例については、「並列パターン ライブラリに関するベスト プラクティス」の「Understand how Cancellation and Exception Handling Affect Object Destruction」を参照してください。

[入力]上

関連トピック

タイトル

説明

方法: キャンセル処理を使用して並列ループを中断する

取り消し処理を使用して並列検索アルゴリズムを実装する方法について説明します。

方法: 例外処理を使用して並列ループを中断する

task_group のクラスを基本ツリー構造の検索アルゴリズムを記述する方法を示します。

同時実行ランタイムでの例外処理

タスク グループ、軽量タスク、および非同期エージェントによってスローされた例外をランタイムが処理するしくみ、およびアプリケーション内で例外に応答する方法を説明します。

タスクの並列化 (同時実行ランタイム)

タスクとタスク グループの関係のほか、アプリケーションで非構造化タスクと構造化タスクを使用する方法について説明します。

並列アルゴリズム

データのコレクションに対して同時処理を実行する並列アルゴリズムについて説明します。

並列パターン ライブラリ (PPL)

並列パターン ライブラリの概要を説明します。

参照

task クラス (同時実行ランタイム)

cancellation_token_source クラス

cancellation_token クラス

task_group クラス

structured_task_group クラス

parallel_for 関数