동시성 런타임에서 예외 처리

업데이트: 2011년 3월

동시성 런타임에서는 C++ 예외 처리를 사용하여 다양한 유형의 오류를 이해합니다. 이러한 오류에는 잘못된 런타임 사용, 리소스 획득 실패와 같은 런타임 오류, 작업 그룹에 제공하는 작업 함수에서 발생하는 오류 등이 있습니다. 이 항목에서는 작업 그룹, 간단한 작업 및 비동기 에이전트에서 throw하는 예외를 런타임에서 처리하는 방법과 응용 프로그램에서 예외에 응답하는 방법을 설명합니다.

단원

  • 작업 그룹 및 병렬 알고리즘

  • 간단한 작업

  • 비동기 에이전트

  • 일반적인 예외

  • 요약

작업 그룹 및 병렬 알고리즘

이 단원에서는 작업 그룹에서 throw된 예외를 런타임에서 처리하는 방법에 대해 설명합니다. 또한 이 단원은 Concurrency::parallel_for와 같이 작업 그룹을 기반으로 하는 병렬 알고리즘에도 적용됩니다.

경고

예외가 종속 작업에 미치는 효과를 이해해야 합니다. 작업 또는 병렬 알고리즘에 예외 처리를 사용하는 방법에 대해 권장되는 방법은 병렬 패턴 라이브러리의 유용한 정보 항목에 있는 취소 및 예외 처리가 개체 소멸에 미치는 영향 이해 단원을 참조하십시오.

작업 그룹에 대한 자세한 내용은 작업 병렬 처리(동시성 런타임)를 참조하십시오. 병렬 알고리즘에 대한 자세한 내용은 병렬 알고리즘을 참조하십시오.

작업 함수에서 Throw되는 예외

작업 함수는 람다 함수, 함수 개체, 런타임에 전달되는 함수 포인터입니다. 작업 그룹에 작업 함수를 전달하면 런타임에서는 별도의 컨텍스트에서 해당 작업 함수를 실행합니다.

Concurrency::task_group 또는 Concurrency::structured_task_group 개체에 전달하는 작업 함수의 본문에서 예외를 throw하면 런타임에서는 해당 예외를 저장하고, Concurrency::task_group::wait, Concurrency::structured_task_group::wait, Concurrency::task_group::run_and_wait 또는 Concurrency::structured_task_group::run_and_wait를 호출하는 컨텍스트로 예외를 마샬링합니다. 또한 런타임에서는 자식 작업 그룹의 작업을 포함하여 작업 그룹에 있는 모든 활성 작업을 중지하고 아직 시작되지 않은 작업을 취소합니다.

다음 예제에서는 예외를 throw하는 작업 함수의 기본 구조를 보여 줍니다. 이 예제에서는 task_group 개체를 사용하여 두 point 개체의 값을 병렬로 출력합니다. print_point 작업 함수는 point 개체의 값을 콘솔에 출력합니다. 입력 값이 NULL인 경우 작업 함수에서 예외가 throw됩니다. 런타임에서는 이 예외를 저장하고 task_group::wait를 호출하는 컨텍스트로 마샬링합니다.

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

using namespace Concurrency;
using namespace std;

// Defines a basic point with X and Y coordinates.
struct point
{
   int X;
   int Y;
};

// Prints the provided point object to the console.
void print_point(point* pt)
{
    // Throw an exception if the value is NULL.
    if (pt == NULL)
    {
        throw exception("point is NULL.");
    }

    // Otherwise, print the values of the point.
    wstringstream ss;
    ss << L"X = " << pt->X << L", Y = " << pt->Y << endl;
    wcout << ss.str();
}

int wmain()
{
   // Create a few point objects.
   point pt = {15, 30};
    point* pt1 = &pt;
    point* pt2 = NULL;

    // Use a task group to print the values of the points.
   task_group tasks;

    tasks.run([&] {
        print_point(pt1);
   });

    tasks.run([&] {
        print_point(pt2);
   });

   // Wait for the tasks to finish. If any task throws an exception,
   // the runtime marshals it to the call to wait.
   try
   {
      tasks.wait();
   }
   catch (const exception& e)
   {
      wcerr << L"Caught exception: " << e.what() << endl;
   }
}

이 예제의 결과는 다음과 같습니다.

X = 15, Y = 30
Caught exception: point is NULL.

작업 그룹에서 예외 처리를 사용하는 전체 예제를 보려면 방법: 예외 처리를 사용하여 병렬 루프 중단을 참조하십시오.

런타임에서 Throw되는 예외

작업 함수뿐만 아니라 런타임을 호출한 결과로 예외가 발생할 수도 있습니다. 런타임에서 throw되는 대부분의 예외는 프로그래밍 오류를 나타냅니다. 일반적으로 이러한 오류는 복구할 수 없으므로 응용 프로그램 코드에 의해 처리되거나 catch되지 않아야 합니다. 그러나 런타임에 정의되어 있는 예외 형식을 이해하면 프로그래밍 오류를 진단하는 데 도움이 될 수 있습니다. 일반적인 예외 단원에서는 일반적인 예외 및 각 예외가 발생하는 조건에 대해 설명합니다.

런타임에서 throw되는 예외에 대한 예외 처리 메커니즘은 작업 함수에서 throw되는 예외의 경우와 같습니다. 예를 들어 Concurrency::receive 함수는 지정된 기간 내에 메시지를 받지 못하면 operation_timed_out을 throw합니다. 작업 그룹에 전달하는 작업 함수에서 receive가 예외를 throw하는 경우 런타임에서는 해당 예외를 저장하고 task_group::wait, structured_task_group::wait, task_group::run_and_wait 또는 structured_task_group::run_and_wait를 호출하는 컨텍스트로 마샬링합니다.

다음 예제에서는 Concurrency::parallel_invoke 알고리즘을 사용하여 두 작업을 병렬로 실행합니다. 첫 번째 작업에서는 5초 동안 기다린 후 메시지 버퍼에 메시지를 보냅니다. 두 번째 작업에서는 receive 함수를 사용하여 첫 번째 작업의 메시지 버퍼에서 메시지를 받을 때까지 3초 동안 기다립니다. receive 함수는 지정된 기간 내에 메시지를 받지 못하면 operation_timed_out을 throw합니다.

// eh-time-out.cpp
// compile with: /EHsc
#include <agents.h>
#include <ppl.h>
#include <iostream>

using namespace Concurrency;
using namespace std;

int wmain()
{
   single_assignment<int> buffer;
   int result;

   try
   {
      // Run two tasks in parallel.
      parallel_invoke(
         // This task waits 5 seconds and then sends a message to 
         // the message buffer.
         [&] {
            wait(5000); 
            send(buffer, 42);
         },
         // This task waits 3 seconds to receive a message.
         // The receive function throws operation_timed_out if it does 
         // not receive a message in the specified time period.
         [&] {
            result = receive(buffer, 3000);
         }
      );

      // Print the result.
      wcout << L"The result is " << result << endl;
   }
   catch (operation_timed_out&)
   {
      wcout << L"The operation timed out." << endl;
   }
}

이 예제의 결과는 다음과 같습니다.

The operation timed out.

응용 프로그램이 비정상적으로 종료되지 않도록 하려면 코드에서 런타임을 호출할 때 예외를 처리해야 합니다. 또한 타사 라이브러리와 같이 동시성 런타임을 사용하는 외부 코드를 호출할 때도 예외를 처리하십시오.

여러 예외

작업 또는 병렬 알고리즘이 여러 예외를 받으면 런타임에서는 이러한 예외 중 하나만 호출 컨텍스트로 마샬링합니다. 런타임에서는 어떤 예외를 마샬링하는지에 대해 보증하지 않습니다.

다음 예제에서는 parallel_for 알고리즘을 사용하여 숫자를 콘솔에 출력합니다. 여기에서는 입력 값이 일부 최소값보다 작거나 최대값보다 크면 예외가 throw됩니다. 이 예제에서는 여러 작업 함수에서 예외를 throw할 수 있습니다.

// eh-multiple.cpp
// compile with: /EHsc
#include <ppl.h>
#include <iostream>
#include <sstream>

using namespace Concurrency;
using namespace std;

int wmain()
{
   const int min = 0;
   const int max = 10;

   // Print values in a parallel_for loop. Use a try-catch block to 
   // handle any exceptions that occur in the loop.
   try
   {
      parallel_for(-5, 20, [min,max](int i)
      {
         // Throw an exeception if the input value is less than the 
         // minimum or greater than the maximum.

         // Otherwise, print the value to the console.

         if (i < min)
         {
            stringstream ss;
            ss << i << ": the value is less than the minimum.";
            throw exception(ss.str().c_str());
         }
         else if (i > max)
         {
            stringstream ss;
            ss << i << ": the value is greater than than the maximum.";
            throw exception(ss.str().c_str());
         }
         else
         {
            wstringstream ss;
            ss << i << endl;
            wcout << ss.str();
         }
      });
   }
   catch (exception& e)
   {
      // Print the error to the console.
      wcerr << L"Caught exception: " << e.what() << endl;
   }  
}

이 예제를 실행하면 다음과 같은 샘플 결과가 출력됩니다.

8
2
9
3
10
4
5
6
7
Caught exception: -5: the value is less than the minimum.

취소

모든 예외가 오류를 나타내는 것은 아닙니다. 예를 들어 검색 알고리즘은 예외 처리를 사용하여 결과를 발견하면 관련 작업을 중지할 수 있습니다. 코드에서 취소 메커니즘을 사용하는 방법에 대한 자세한 내용은 PPL에서의 취소를 참조하십시오.

[맨 위로 이동]

간단한 작업

간단한 작업은 Concurrency::Scheduler 개체에서 직접 예약하는 작업입니다. 간단한 작업은 일반적인 작업보다 오버헤드가 적습니다. 그러나 런타임에서는 간단한 작업에서 throw되는 예외를 catch하지 않습니다. 대신 처리되지 않은 예외 처리기에 의해 예외가 catch되어 기본적으로 프로세스가 종료됩니다. 그러므로 응용 프로그램에서 적절한 오류 처리 메커니즘을 사용해야 합니다. 간단한 작업에 대한 자세한 내용은 작업 스케줄러(동시성 런타임)를 참조하십시오.

[맨 위로 이동]

비동기 에이전트

간단한 작업과 마찬가지로 런타임에서는 비동기 에이전트에서 throw되는 예외를 관리하지 않습니다.

다음 예제에서는 Concurrency::agent에서 파생되는 클래스에서 예외를 처리하는 한 가지 방법을 보여 줍니다. 이 예제에서는 points_agent 클래스를 정의합니다. points_agent::run 메서드는 메시지 버퍼에서 point 개체를 읽고 콘솔에 출력합니다. run 메서드는 NULL 포인터를 받으면 예외를 throw합니다.

run 메서드는 try-catch 블록의 모든 작업을 포함합니다. catch 블록은 예외를 메시지 버퍼에 저장합니다. 응용 프로그램에서는 에이전트가 끝난 후 이 버퍼를 읽어 해당 에이전트에서 오류가 발생했는지 여부를 검사합니다.

// eh-agents.cpp
// compile with: /EHsc
#include <agents.h>
#include <iostream>

using namespace Concurrency;
using namespace std;

// Defines a point with x and y coordinates.
struct point
{
   int X;
   int Y;
};

// Informs the agent to end processing.
point sentinel = {0,0};

// An agent that prints point objects to the console.
class point_agent : public agent
{
public:
   explicit point_agent(unbounded_buffer<point*>& points)
      : _points(points)
   { 
   }

   // Retrieves any exception that occured in the agent.
   bool get_error(exception& e)
   {
      return try_receive(_error, e);
   }

protected:
   // Performs the work of the agent.
   void run()
   {
      // Perform processing in a try block.
      try
      {
         // Read from the buffer until we reach the sentinel value.
         while (true)
         {
            // Read a value from the message buffer.
            point* r = receive(_points);

            // In this example, it is an error to receive a 
            // NULL point pointer. In this case, throw an exception.
            if (r == NULL)
            {
               throw exception("point must not be NULL");
            }
            // Break from the loop if we receive the 
            // sentinel value.
            else if (r == &sentinel)
            {
               break;
            }
            // Otherwise, do something with the point.
            else
            {
               // Print the point to the console.
               wcout << L"X: " << r->X << L" Y: " << r->Y << endl;
            }
         }
      }
      // Store the error in the message buffer.
      catch (exception& e)
      {
         send(_error, e);
      }

      // Set the agent status to done.
      done();
   }

private:
   // A message buffer that receives point objects.
   unbounded_buffer<point*>& _points;

   // A message buffer that stores error information.
   single_assignment<exception> _error;
};

int wmain()
{  
   // Create a message buffer so that we can communicate with
   // the agent.
   unbounded_buffer<point*> buffer;

   // Create and start a point_agent object.
   point_agent a(buffer);
   a.start();

   // Send several points to the agent.
   point r1 = {10, 20};
   point r2 = {20, 30};
   point r3 = {30, 40};

   send(buffer, &r1);
   send(buffer, &r2);
   // To illustrate exception handling, send the NULL pointer to the agent.
   send(buffer, reinterpret_cast<point*>(NULL));
   send(buffer, &r3);
   send(buffer, &sentinel);

   // Wait for the agent to finish.
   agent::wait(&a);

   // Check whether the agent encountered an error.
   exception e;
   if (a.get_error(e))
   {
      cout << "error occured in agent: " << e.what() << endl;
   }

   // Print out agent status.
   wcout << L"the status of the agent is: ";
   switch (a.status())
   {
   case agent_created:
      wcout << L"created";
      break;
   case agent_runnable:
      wcout << L"runnable";
      break;
   case agent_started:
      wcout << L"started";
      break;
   case agent_done:
      wcout << L"done";
      break;
   case agent_canceled:
      wcout << L"canceled";
      break;
   default:
      wcout << L"unknown";
      break;
   }
   wcout << endl;
}

이 예제의 결과는 다음과 같습니다.

X: 10 Y: 20
X: 20 Y: 30
error occured in agent: point must not be NULL
the status of the agent is: done

try-catch 블록은 while 루프 외부에 있으므로 에이전트는 첫 번째 오류가 발생하면 처리를 종료합니다. try-catch 블록이 while 루프 내부에 있었다면 오류가 발생한 후에도 에이전트가 계속 실행됩니다.

이 예제에서는 다른 구성 요소에서 에이전트가 실행될 때 오류가 발생하는지 모니터링할 수 있도록 메시지 버퍼에 예외를 저장합니다. 여기에서는 Concurrency::single_assignment 개체를 사용하여 오류를 저장합니다. 에이전트가 여러 예외를 처리하는 경우 single_assignment 클래스는 첫 번째로 전달되는 메시지만 저장합니다. 마지막 예외만 저장하려면 Concurrency::overwrite_buffer 클래스를 사용하고, 모든 예외를 저장하려면 Concurrency::unbounded_buffer 클래스를 사용해야 합니다. 이러한 메시지 블록에 대한 자세한 내용은 비동기 메시지 블록을 참조하십시오.

비동기 에이전트에 대한 자세한 내용은 비동기 에이전트를 참조하십시오.

[맨 위로 이동]

일반적인 예외

다음 표에서는 동시성 런타임의 일반적인 예외 클래스를 보여 주고 각 예외가 throw되는 조건에 대해 설명합니다. operation_timed_outunsupported_os를 제외한 대부분의 예외 형식은 프로그래밍 오류를 나타냅니다. 일반적으로 이러한 오류는 복구할 수 없으므로 응용 프로그램 코드에 의해 처리되거나 catch되지 않아야 합니다. 프로그래밍 오류를 진단해야 하는 경우에만 응용 프로그램 코드에서 복구할 수 없는 오류를 처리하거나 catch하는 것이 좋습니다.

예외 클래스

조건

bad_target

메시지 블록에 잘못된 포인터를 전달하는 경우

context_self_unblock

컨텍스트에서 자신을 차단 해제하려고 한 경우

context_unblock_unbalanced

런타임에서 컨텍스트의 차단을 해제하려고 했지만 해당 컨텍스트가 이미 차단 해제된 경우

default_scheduler_exists

기본 스케줄러가 이미 있는데 다른 스케줄러를 기본 스케줄러로 설정하려고 한 경우

improper_lock

잠금을 잘못 가져온 경우

improper_scheduler_attach

같은 스케줄러에 컨텍스트가 여러 번 연결된 경우

improper_scheduler_detach

런타임에서 내부적으로 관리되는 컨텍스트가 스케줄러에서 분리되었거나 컨텍스트가 스케줄러에 연결되지 않은 경우

improper_scheduler_reference

종료 중인 스케줄러에서 참조 카운터를 증가시킨 컨텍스트가 스케줄러의 내부 멤버가 아닌 경우

invalid_link_target

같은 개체가 메시지 블록에 두 번 이상 연결되는 경우

invalid_multiple_scheduling

완료하지 않은 작업이 두 번 이상 예약된 경우

invalid_operation

런타임에서 잘못된 작업을 수행한 경우

invalid_oversubscribe_operation

초과 구독이 설정되지 않았는데 해제된 경우

invalid_scheduler_policy_key

잘못된 정책 키가 Concurrency::SchedulerPolicy 개체에 제공된 경우

invalid_scheduler_policy_thread_specification

SchedulerPolicy 개체의 최대 동시성 수준이 최소 동시성 수준보다 작게 지정된 경우

invalid_scheduler_policy_value

잘못된 정책 값이 SchedulerPolicy 개체에 제공된 경우

message_not_found

메시지 블록에서 요청된 메시지를 찾을 수 없는 경우

missing_wait

Concurrency::task_group::wait 메서드 또는 Concurrency::structured_task_group::wait 메서드가 호출되기 전에 작업 그룹 개체가 소멸된 경우

nested_scheduler_missing_detach

중첩 스케줄러가 부모에서 올바르게 분리되지 않은 경우

operation_timed_out

지정된 기간 내에 작업이 완료되지 않은 경우

scheduler_not_attached

스케줄러에 연결되어 있지 않은 컨텍스트를 스케줄러에서 분리하려고 한 경우

scheduler_resource_allocation_error

런타임에서 중요한 리소스(예: 운영 체제에서 제공하는 리소스)를 얻지 못한 경우

unsupported_os

현재 운영 체제에서 런타임이 지원되지 않는 경우

[맨 위로 이동]

요약

작업에서 예외를 throw하면 런타임에서는 해당 예외를 저장하고, 작업 그룹이 끝날 때까지 기다리는 컨텍스트로 예외를 마샬링합니다. 간단한 작업 및 에이전트와 같은 구성 요소의 경우 런타임에서는 예외를 자동으로 관리하지 않습니다. 이러한 경우에는 사용자 고유의 예외 처리 메커니즘을 구현해야 합니다.

[맨 위로 이동]

참고 항목

개념

동시성 런타임

작업 병렬 처리(동시성 런타임)

병렬 알고리즘

PPL에서의 취소

작업 스케줄러(동시성 런타임)

비동기 에이전트

변경 기록

날짜

변경 내용

이유

2011년 3월

예외가 종속 작업에 영향을 줄 수 있는 효과에 대한 참조 사항을 추가했습니다.

향상된 기능 관련 정보