PPL에서의 취소

이 문서에서는 PPL(병렬 패턴 라이브러리)에서 취소의 역할, 병렬 작업을 취소하는 방법 및 병렬 작업이 취소될 경우를 확인하는 방법에 대해 설명합니다.

참고 항목

런타임에서는 예외 처리를 사용하여 취소를 구현합니다. 코드에서 이들 예외를 catch 또는 처리하지 마세요. 또한 작업에 대한 함수 본문에 예외로부터 안전한 코드를 작성하는 것이 좋습니다. 예를 들어 RAII(리소스 획득 초기화) 패턴을 사용하여 태스크 본문에서 예외가 throw될 때 리소스가 올바르게 처리되도록 할 수 있습니다. RAII 패턴을 사용하여 취소 가능한 작업에서 리소스를 클린 전체 예제는 연습: 사용자 인터페이스 스레드에서 작업 제거를 참조하세요.

요점

이 문서에서 다루는 내용

병렬 작업 트리

PPL에서는 작업 및 작업 그룹을 사용하여 세분화된 작업 및 계산을 관리합니다. 작업 그룹을 중첩하여 병렬 작업의 트리를 구성할 수 있습니다. 다음 그림은 병렬 작업 트리를 보여 줍니다. 이 그림에서 tg1tg2는 작업 그룹을 나타내고, t1, t2, t3, t4t5는 작업 그룹에서 수행하는 작업을 나타냅니다.

A parallel work tree.

다음 예제에서는 그림의 트리를 만드는 데 필요한 코드를 보여 줍니다. 이 예제 tg1tg2 에서는 동시성::structured_task_group 개체, t1, t3t2t5t4동시성::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();   
}

동시성::task_group 클래스를 사용하여 유사한 작업 트리를 만들 수도 있습니다. 동시성::task 클래스는 작업 트리의 개념도 지원합니다. 그러나 task 트리는 종속성 트리입니다. task 트리에서 미래 작업은 현재 작업 후에 완료됩니다. 작업 그룹 트리에서 내부 작업은 외부 작업 전에 완료됩니다. 작업과 작업 그룹 간의 차이점에 대한 자세한 내용은 작업 병렬 처리를 참조하세요.

[맨 위로 이동]

병렬 작업 취소

병렬 작업을 취소하는 방법은 여러 가지가 있습니다. 여러 방법 중에 취소 토큰을 사용하는 것이 좋습니다. 작업 그룹은 동시성::task_group::cancel 메서드 및 동시성::structured_task_group::cancel 메서드도 지원합니다. 마지막 방법은 작업 함수의 본문에서 예외를 throw하는 것입니다. 어떤 방법을 선택하더라도 취소는 즉시 발생하지 않습니다. 작업 또는 작업 그룹이 취소된 경우 새 작업이 시작되지 않지만 활성 작업은 취소에 대해 검사 응답해야 합니다.

병렬 작업을 취소하는 더 많은 예제는 연습: 커넥트 작업 및 XML HTTP 요청 사용, 방법: 취소를 사용하여 병렬 루프에서 중단, 방법: 예외 처리를 사용하여 병렬 루프에서 중단을 참조하세요.

취소 토큰을 사용하여 병렬 작업 취소

task, task_groupstructured_task_group 클래스는 취소 토큰을 사용하는 방법으로 취소 기능을 지원합니다. PPL은 이 목적을 위해 동시성::cancellation_token_source동시성::cancellation_token 클래스를 정의합니다. 취소 토큰을 사용하여 작업을 취소하면 런타임에서 해당 토큰을 구독하는 새 작업을 시작하지 않습니다. 이미 활성 상태인 작업은 is_canceled 멤버 함수를 사용하여 취소 토큰을 모니터링하고 가능하면 중지할 수 있습니다.

취소를 시작하려면 동시성::cancellation_token_source::cancel 메서드를 호출합니다. 다음 방법으로 취소에 응답합니다.

  • 개체의 경우 task 동시성::cancel_current_task 함수를 사용합니다. cancel_current_task는 현재 작업 및 모든 값 기반 연속을 취소합니다. (작업 또는 연속 작업과 연결된 취소 토큰 은 취소하지 않습니다.)

  • 작업 그룹 및 병렬 알고리즘의 경우 동시성::is_current_task_group_canceling 함수를 사용하여 취소를 감지하고 이 함수가 반환될 때 작업 본문에서 가능한 한 빨리 반환합니다true. (작업 그룹에서 cancel_current_task를 호출하지 마세요.)

다음 예제에서는 작업 취소의 첫 번째 기본 패턴을 보여 줍니다. 작업 본문에서는 때때로 루프 내부에 취소가 있는지 확인합니다.

// task-basic-cancellation.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <concrt.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 (token.is_canceled())
            {
                // 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 함수가 throw하므로 현재 루프 또는 함수에서 명시적으로 반환할 필요가 없습니다.

또는 동시성::interruption_point 함수를 cancel_current_task대신 호출할 수 있습니다.

작업을 취소됨 상태로 전환하므로 취소에 응답할 때 cancel_current_task를 호출해야 합니다. cancel_current_task를 호출하지 않고 조기에 반환하는 경우 작업이 완료됨 상태로 전환되고 값 기반 연속이 실행됩니다.

주의

코드에서 task_canceled를 throw하지 마세요. 대신 cancel_current_task를 호출하세요.

작업이 취소된 상태로 끝나면 동시성::task::get 메서드는 동시성::task_canceled throw합니다. (반대로 동시성::task::wait는 task_상태::canceled를 반환하며 throw하지 않습니다.) 다음 예제에서는 작업 기반 연속 작업에 대한 이 동작을 보여 줍니다. 작업 기반 연속은 선행 작업이 취소되어도 항상 호출됩니다.

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

값 기반 연속은 명시적 토큰을 사용하여 생성된 경우가 아니면 선행 작업의 토큰을 상속하므로 선행 작업이 실행 중이어도 연속이 즉시 취소됨 상태로 전환됩니다. 따라서 취소 후에 선행 작업에서 throw되는 예외는 연속 작업에 전파되지 않습니다. 취소는 항상 선행 작업의 상태를 재정의합니다. 다음 예제는 이전 예제와 비슷하지만 값 기반 연속에 대한 동작을 보여 줍니다.

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

주의

취소 토큰 task 을 생성자 또는 동시성::create_task 함수에 전달하지 않으면 해당 작업을 취소할 수 없습니다. 또한 모든 작업을 동시에 취소하려면 중첩된 작업(다른 작업의 본문 내에 생성된 작업)의 생성자에 같은 취소 토큰을 전달해야 합니다.

취소 토큰이 취소될 때 임의의 코드를 실행해야 할 수 있습니다. 예를 들어 사용자가 사용자 인터페이스에서 취소 단추를 선택하여 작업을 취소하는 경우 사용자가 다른 작업을 시작할 때까지 해당 단추를 사용하지 않도록 설정할 수 있습니다. 다음 예제에서는 동시성::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 개체를 연속 작업에 제공하지 않으면 연속은 다음 방법으로 선행 작업에서 취소 토큰을 상속합니다.

  • 값 기반 연속은 항상 선행 작업의 취소 토큰을 상속합니다.

  • 작업 기반 연속은 항상 선행 작업의 취소 토큰을 상속하지 않습니다. 작업 기반 연속을 취소 가능하도록 설정하는 유일한 방법은 취소 토큰을 명시적으로 전달하는 것입니다.

결함이 있는 작업(예외를 throw하는 작업)은 이들 동작에 영향을 미치지 않습니다. 이 경우 값 기반 연속 작업이 취소됩니다. 작업 기반 연속 작업이 취소되지 않습니다.

주의

다른 작업에서 생성된 작업(중첩된 작업)은 부모 작업의 취소 토큰을 상속하지 않습니다. 값 기반 연속만 선행 작업의 취소 토큰을 상속합니다.

task_group 또는 structured_task_group 개체의 생성자에 취소 토큰을 제공할 수도 있습니다. 이 방법의 중요한 측면은 자식 작업 그룹이 이 취소 토큰을 상속한다는 점입니다. 동시성::run_with_cancellation_token 함수를 사용하여 호출parallel_for을 실행하여 이 개념을 보여 주는 예제는 이 문서의 뒷부분에 있는 병렬 알고리즘 취소를 참조하세요.

[맨 위로 이동]

취소 토큰 및 작업 컴퍼지션

동시성::when_all동시성::when_any 함수는 여러 작업을 구성하여 일반적인 패턴을 구현하는 데 도움이 될 수 있습니다. 이 섹션에서는 이들 함수에서 취소 토큰을 사용하는 방법을 보여 줍니다.

및 함수에 취소 토큰을 when_allwhen_any 제공하는 경우 해당 취소 토큰이 취소되거나 참가자 작업 중 하나가 취소된 상태로 끝나거나 예외를 throw하는 경우에만 해당 함수가 취소됩니다.

when_all 함수는 취소 토큰을 제공하지 않을 경우 전체 작업을 구성하는 각 작업에서 취소 토큰을 상속합니다. 이러한 토큰이 취소되고 참가자 작업 중 하나 이상이 아직 시작되지 않았거나 실행 중일 때 반환 when_all 되는 작업이 취소됩니다. 작업 중 하나가 예외를 throw할 때 비슷한 동작이 발생합니다. 반환 when_all 된 작업은 해당 예외로 즉시 취소됩니다.

런타임에서는 해당 작업이 완료될 때 when_any 함수에서 반환되는 작업에 대한 취소 토큰을 선택합니다. 완료됨 상태로 종료된 참가 작업이 없거나 하나 이상의 작업이 예외를 throw하면 throw된 작업의 하나가 when_any를 완료하기 위해 선택되고 해당 토큰이 최종 작업의 토큰으로 선택됩니다. 완료됨 상태로 종료된 작업이 두 개 이상이면 when_any 작업에서 반환된 작업이 완료됨 상태로 끝납니다. 다른 실행 중인 작업이 나중에 완료되더라도 when_any에서 반환된 작업이 즉시 취소되지 않도록 런타임에서는 완료 시 토큰이 취소되지 않은 완료된 작업을 선택하려고 시도합니다.

[맨 위로 이동]

cancel 메서드를 사용하여 병렬 작업 취소

동시성::task_group::cancel동시성::structured_task_group::cancel 메서드는 작업 그룹을 취소된 상태로 설정합니다. cancel이 호출되고 나면 작업 그룹이 미래 작업을 시작하지 않습니다. cancel 메서드는 여러 자식 작업을 통해 호출할 수 있습니다. 취소된 작업으로 인해 동시성::task_group::wait동시성::structured_task_group::wait 메서드가 동시성::canceled를 반환합니다.

작업 그룹이 취소된 경우 각 자식 태스크에서 런타임으로의 호출은 중단 지점을 트리거할 수 있으며, 이로 인해 런타임이 throw되고 내부 예외 형식이 catch되어 활성 작업이 취소됩니다. 동시성 런타임은 특정 중단 지점을 정의하지 않고 런타임에 대한 호출에서 발생할 수 없습니다. 런타임에서는 취소를 수행하기 위해 throw할 예외를 처리해야 합니다. 따라서 작업 본문에서 알 수 없는 예외를 처리하지 마세요.

자식 작업은 시간이 오래 걸리는 작업을 수행하지만 런타임으로 호출하지 않을 경우 주기적으로 취소를 확인하고 시기적절하게 종료되어야 합니다. 다음 예제에서는 작업이 취소되는 시기를 결정하는 한 가지 방법을 보여 줍니다. 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번째 반복마다 취소를 검사. 취소를 확인하는 빈도는 수행하는 작업량 및 작업이 취소에 응답해야 하는 속도에 따라 달라집니다.

부모 작업 그룹 개체에 액세스할 수 없는 경우 동시성::is_current_task_group_canceling 함수를 호출하여 부모 작업 그룹이 취소되었는지 여부를 확인합니다.

cancel 메서드는 자식 작업에만 영향을 미칩니다. 예를 들어 병렬 작업 트리 그림에서 작업 그룹 tg1을 취소하면 트리의 모든 작업(t1, t2, t3, t4t5)이 영향을 받습니다. 중첩된 작업 그룹 tg2를 취소하면 t4t5 작업만 영향을 받습니다.

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을 취소한다는 점을 제외하고 첫 번째 예제와 비슷합니다. 이는 트리의 모든 작업(t1, t2, t3, t4t5)에 영향을 미칩니다.

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::cancel동시성::structured_task_group::is_canceling 메서드입니다. 자식 작업에서는 이들 메서드를 호출하여 부모 작업 그룹을 취소하고 취소가 있는지 확인할 수 있습니다.

주의

취소 토큰을 사용하여 task 개체의 자식으로 실행되는 작업 그룹에서 수행되는 작업을 취소할 수 있지만 task_group::cancel 또는 structured_task_group::cancel 메서드를 사용하여 작업 그룹에서 실행되는 task 개체를 취소할 수는 없습니다.

[맨 위로 이동]

예외를 사용하여 병렬 작업 취소

병렬 작업 트리를 취소할 때 취소 토큰 및 cancel 메서드를 사용하는 것이 예외 처리보다 더 효율적입니다. 취소 토큰 및 cancel 메서드는 하향식으로 작업과 모든 자식 작업을 취소합니다. 반대로 예외 처리는 상향식으로 작동하고 예외가 위쪽으로 전파될 때 각 자식 작업 그룹을 개별적으로 취소해야 합니다. 예외 처리 항목에서는 동시성 런타임에서 예외를 사용하여 오류를 전달하는 방법을 설명합니다. 그러나 일부 예외는 오류를 나타내지 않습니다. 예를 들어 검색 알고리즘에서는 결과를 발견하면 연결된 작업을 취소할 수 있습니다. 하지만 앞에서 설명한 대로 예외 처리는 cancel 메서드를 사용하여 병렬 작업을 취소하는 방법보다 덜 효율적입니다.

주의

필요한 경우에만 예외를 사용하여 병렬 작업을 취소하는 것이 좋습니다. 취소 토큰 및 작업 그룹 cancel 메서드는 더 효율적이고 오류가 발생할 가능성이 감소합니다.

작업 그룹에 전달하는 작업 함수의 본문에서 예외를 throw하면 런타임에서는 해당 예외를 저장하고 작업 그룹이 완료되기를 기다리는 컨텍스트에 해당 예외를 마샬링합니다. cancel 메서드처럼 런타임에서는 아직 시작되지 않은 모든 작업을 무시하고 새 작업을 수락하지 않습니다.

이 세 번째 예제는 작업 t4가 예외를 throw하여 작업 그룹 tg2를 취소한다는 점을 제외하고 두 번째 예제와 비슷합니다. 이 예제에서는 작업 그룹이 자식 작업이 완료되기를 기다리는 경우 취소를 위해 블록을 검사trycatch-.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;
}

네 번째 예제에서는 예외 처리를 사용하여 전체 작업 트리를 취소합니다. 예제에서는 작업 그룹 tg2가 자식 작업을 기다릴 때가 아니라 작업 그룹 tg1이 자식 작업이 완료되기를 기다릴 때 예외를 catch합니다. 두 번째 예제처럼 이로 인해 트리의 두 작업 그룹 tg1tg2는 모두 취소됨 상태로 전환됩니다.

// 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::waitstructured_task_group::wait 메서드는 자식 작업이 예외를 throw할 때 throw하므로 이들 메서드에서 반환 값을 받을 수 없습니다.

[맨 위로 이동]

병렬 알고리즘 취소

PPL의 병렬 알고리즘(예: parallel_for)은 작업 그룹에 빌드됩니다. 따라서 대부분 같은 방법을 사용하여 병렬 알고리즘을 취소할 수 있습니다.

다음 예제에서는 병렬 알고리즘을 취소하는 여러 가지 방법을 보여 줍니다.

다음 예제에서는 run_with_cancellation_token 함수를 사용하여 parallel_for 알고리즘을 호출합니다. 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
*/

다음 예제에서는 동시성::structured_task_group::run_and_wait 메서드를 사용하여 알고리즘을 호출합니다parallel_for. 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.
   }
});

각 취소 방법에는 다른 방법에 비해 장점이 있습니다. 특정 요구 사항에 맞는 방법을 선택하세요.

[맨 위로 이동]

취소를 사용하지 않는 경우

관련 작업 그룹의 각 멤버가 시기적절하게 종료될 수 있으면 취소를 사용하는 것이 좋습니다. 그러나 애플리케이션에 대해 취소가 적절하지 않을 수 있는 몇 가지 시나리오가 있습니다. 예를 들어 작업 취소는 공동 작업이므로 개별 작업이 차단된 경우에도 전체 작업 집합은 취소되지 않습니다. 예를 들어 한 작업이 아직 시작되지 않았지만 이 작업이 다른 활성 작업을 차단 해제할 경우 작업 그룹이 취소되면 이 작업은 시작되지 않습니다. 이로 인해 애플리케이션에서 교착 상태가 발생할 수 있습니다. 취소 사용이 적절하지 않을 수 있는 두 번째 예는 작업이 취소되지만 자식 작업이 리소스 해제와 같은 중요한 작업을 수행하는 경우입니다. 전체 작업 집합은 부모 작업이 취소될 때 취소되므로 해당 작업이 실행되지 않습니다. 이 점을 보여 주는 예제는 병렬 패턴 라이브러리 항목의 모범 사례에서 취소 및 예외 처리가 개체 소멸 에 미치는 영향 이해 섹션을 참조하세요.

[맨 위로 이동]

제목 설명
방법: 취소를 사용하여 병렬 루프 중단 취소를 사용하여 병렬 검색 알고리즘을 구현하는 방법을 보여 줍니다.
방법: 예외 처리를 사용하여 병렬 루프 중단 task_group 클래스를 사용하여 기본적인 트리 구조에 대한 검색 알고리즘을 작성하는 방법을 보여 줍니다.
예외 처리 작업 그룹, 간단한 작업 및 비동기 에이전트에서 throw한 예외를 런타임에서 처리하는 방법 및 애플리케이션에 예외에 응답하는 방법을 설명합니다.
작업 병렬 처리 작업이 작업 그룹과 관련되는 방식 및 애플리케이션에서 구조화되지 않은 작업과 구조화된 작업을 사용하는 방법을 설명합니다.
병렬 알고리즘 데이터 컬렉션에 대한 작업을 동시에 수행하는 병렬 알고리즘을 설명합니다.
PPL(병렬 패턴 라이브러리) 병렬 패턴 라이브러리에 대해 간략하게 설명합니다.

참조

task 클래스(동시성 런타임)

cancellation_token_source 클래스

cancellation_token 클래스

task_group 클래스

structured_task_group 클래스

parallel_for 함수