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

동시성 런타임은 C++ 예외 처리를 사용하여 다양한 종류의 오류를 전달합니다. 이러한 오류에는 런타임의 잘못된 사용, 리소스 획득 실패와 같은 런타임 오류 및 작업 및 작업 그룹에 제공하는 작업 함수에서 발생하는 오류가 포함됩니다. 태스크 또는 작업 그룹이 예외를 throw하면 런타임은 해당 예외를 유지하고 태스크 또는 작업 그룹이 완료되기를 기다리는 컨텍스트로 마샬링합니다. 간단한 작업 및 에이전트와 같은 구성 요소의 경우 런타임은 예외를 관리하지 않습니다. 이러한 경우 고유한 예외 처리 메커니즘을 구현해야 합니다. 이 항목에서는 런타임이 태스크, 작업 그룹, 간단한 작업 및 비동기 에이전트에서 throw되는 예외를 처리하는 방법과 애플리케이션의 예외에 대응하는 방법을 설명합니다.

요점

  • 태스크 또는 작업 그룹이 예외를 throw하면 런타임은 해당 예외를 유지하고 태스크 또는 작업 그룹이 완료되기를 기다리는 컨텍스트로 마샬링합니다.

  • 가능하면 동시성::task::getconcurrency::task::wait에 대한 모든 호출을 블록으로 try/catch 묶어 복구할 수 있는 오류를 처리합니다. 태스크가 예외를 throw하고 해당 예외가 태스크, 연속 작업 중 하나 또는 기본 앱에 의해 catch되지 않으면 런타임이 앱을 종료합니다.

  • 작업 기반 연속은 항상 실행됩니다. 선행 작업이 성공적으로 완료되었는지, 예외를 throw했는지 또는 취소되었는지는 중요하지 않습니다. 선행 작업이 throw되거나 취소되면 값 기반 연속 작업이 실행되지 않습니다.

  • 작업 기반 연속 작업은 항상 실행되므로 연속 체인의 끝에 작업 기반 연속을 추가할지 여부를 고려합니다. 이렇게 하면 코드에서 모든 예외를 관찰할 수 있습니다.

  • 동시성::task::get을 호출하면 런타임이 동시성::task_canceled throw되고 해당 작업이 취소됩니다.

  • 런타임은 간단한 작업 및 에이전트에 대한 예외를 관리하지 않습니다.

이 문서에서 다루는 내용

작업 및 연속

이 섹션에서는 런타임이 동시성::task 개체 및 해당 연속으로 throw되는 예외를 처리하는 방법을 설명합니다. 작업 및 연속 모델에 대한 자세한 내용은 작업 병렬 처리를 참조하세요.

개체에 전달하는 task 작업 함수 본문에 예외를 throw하면 런타임은 해당 예외를 저장하고 동시성::task::get 또는 concurrency::task::wait를 호출하는 컨텍스트로 마샬링합니다. 작업 병렬 처리 문서에서는 작업 기반 및 값 기반 연속 작업을 설명하지만 요약하면 값 기반 연속은 형식 T 의 매개 변수를 사용하고 작업 기반 연속은 형식task<T>의 매개 변수를 사용합니다. throw되는 작업에 값 기반 연속이 하나 이상 있는 경우 해당 연속 작업은 실행되도록 예약되지 않습니다. 다음은 이 동작에 대한 예입니다.

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

using namespace concurrency;
using namespace std;

int wmain()
{
    wcout << L"Running a task..." << endl;
    // Create a task that throws.
    auto t = create_task([]
    {
        throw exception();
    });
    
    // Create a continuation that prints its input value.
    auto continuation = t.then([]
    {
        // We do not expect this task to run because
        // the antecedent task threw.
        wcout << L"In continuation task..." << endl;
    });

    // Wait for the continuation to finish and handle any 
    // error that occurs.
    try
    {
        wcout << L"Waiting for tasks to finish..." << endl;
        continuation.wait();

        // Alternatively, call get() to produce the same result.
        //continuation.get();
    }
    catch (const exception& e)
    {
        wcout << L"Caught exception." << endl;
    }
}
/* Output:
    Running a task...
    Waiting for tasks to finish...
    Caught exception.
*/

작업 기반 연속 작업을 사용하면 선행 작업에서 throw되는 모든 예외를 처리할 수 있습니다. 작업 기반 연속은 항상 실행됩니다. 작업이 성공적으로 완료되었는지, 예외를 throw했는지 또는 취소되었는지는 중요하지 않습니다. 태스크가 예외를 throw하면 작업 기반 연속 작업이 실행되도록 예약됩니다. 다음 예제에서는 항상 throw되는 작업을 보여 줍니다. 작업에는 두 개의 연속 작업이 있습니다. 하나는 값 기반이고 다른 하나는 작업 기반입니다. 작업 기반 예외는 항상 실행되므로 선행 작업에서 throw되는 예외를 catch할 수 있습니다. 두 연속 작업이 모두 완료되기를 기다리는 예제에서는 태스크 예외가 항상 throw되거나 호출되면 예외가 다시 throw task::gettask::wait 됩니다.

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

using namespace concurrency;
using namespace std;

int wmain()
{    
    wcout << L"Running a task..." << endl;
    // Create a task that throws.
    auto t = create_task([]() -> int
    {
        throw exception();
        return 42;
    });

    //
    // Attach two continuations to the task. The first continuation is  
    // value-based; the second is task-based.

    // Value-based continuation.
    auto c1 = t.then([](int n)
    {
        // We don't expect to get here because the antecedent 
        // task always throws.
        wcout << L"Received " << n << L'.' << endl;
    });

    // Task-based continuation.
    auto c2 = t.then([](task<int> previousTask)
    {
        // We do expect to get here because task-based continuations
        // are scheduled even when the antecedent task throws.
        try
        {
            wcout << L"Received " << previousTask.get() << L'.' << endl;
        }
        catch (const exception& e)
        {
            wcout << L"Caught exception from previous task." << endl;
        }
    });

    // Wait for the continuations to finish.
    try
    {
        wcout << L"Waiting for tasks to finish..." << endl;
        (c1 && c2).wait();
    }
    catch (const exception& e)
    {
        wcout << L"Caught exception while waiting for all tasks to finish." << endl;
    }
}
/* Output:
    Running a task...
    Waiting for tasks to finish...
    Caught exception from previous task.
    Caught exception while waiting for all tasks to finish.
*/

작업 기반 연속 작업을 사용하여 처리할 수 있는 예외를 catch하는 것이 좋습니다. 작업 기반 연속 작업은 항상 실행되므로 연속 체인의 끝에 작업 기반 연속을 추가할지 여부를 고려합니다. 이렇게 하면 코드에서 모든 예외를 관찰할 수 있습니다. 다음 예제에서는 기본 값 기반 연속 체인을 보여줍니다. 체인의 세 번째 작업이 throw되므로 체인 뒤에 있는 모든 값 기반 연속 작업은 실행되지 않습니다. 그러나 최종 연속 작업은 작업 기반이므로 항상 실행됩니다. 이 마지막 연속 작업은 세 번째 작업에서 throw되는 예외를 처리합니다.

가장 구체적인 예외를 catch하는 것이 좋습니다. catch할 특정 예외가 없는 경우 이 최종 작업 기반 연속 작업을 생략할 수 있습니다. 예외가 다시 처리되지 기본 앱을 종료할 수 있습니다.

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

using namespace concurrency;
using namespace std;

int wmain()
{
    int n = 1;
    create_task([n]
    {
        wcout << L"In first task. n = ";
        wcout << n << endl;
        
        return n * 2;

    }).then([](int n)
    {
        wcout << L"In second task. n = ";
        wcout << n << endl;

        return n * 2;

    }).then([](int n)
    {
        wcout << L"In third task. n = ";
        wcout << n << endl;

        // This task throws.
        throw exception();
        // Not reached.
        return n * 2;

    }).then([](int n)
    {
        // This continuation is not run because the previous task throws.
        wcout << L"In fourth task. n = ";
        wcout << n << endl;

        return n * 2;

    }).then([](task<int> previousTask)
    {
        // This continuation is run because it is task-based.
        try
        {
            // The call to task::get rethrows the exception.
            wcout << L"In final task. result = ";
            wcout << previousTask.get() << endl;
        }
        catch (const exception&)
        {
            wcout << L"<exception>" << endl;
        }
    }).wait();
}
/* Output:
    In first task. n = 1
    In second task. n = 2
    In third task. n = 4
    In final task. result = <exception>
*/

동시성::task_completion_event::set_exception 메서드를 사용하여 예외를 작업 완료 이벤트와 연결할 수 있습니다. 작업 병렬 처리 문서에서는 동시성::task_completion_event 클래스에 대해 자세히 설명합니다.

concurrency::task_canceled .에 관련된 중요한 런타임 예외 형식입니다 task. 호출 task::get 할 때 런타임이 task_canceled throw되고 해당 작업이 취소됩니다. task::wait 반대로 task_상태::canceled를 반환하며 throw하지 않습니다. 작업 기반 연속 또는 호출 task::get시 이 예외를 catch하고 처리할 수 있습니다. 작업 취소에 대한 자세한 내용은 PPL의 취소를 참조하세요.

주의

코드에서 task_canceled를 throw하지 마세요. 대신 동시성::cancel_current_task 호출합니다.

태스크가 예외를 throw하고 해당 예외가 태스크, 연속 작업 중 하나 또는 기본 앱에 의해 catch되지 않으면 런타임이 앱을 종료합니다. 애플리케이션이 충돌하는 경우 C++ 예외가 throw되면 중단되도록 Visual Studio를 구성할 수 있습니다. 처리되지 않은 예외의 위치를 진단한 후 작업 기반 연속 작업을 사용하여 처리합니다.

이 문서에서 런타임에 의해 throw된 예외 섹션 은 런타임 예외를 사용하는 방법을 자세히 설명합니다.

[맨 위로 이동]

작업 그룹 및 병렬 알고리즘

이 섹션에서는 런타임이 작업 그룹에서 throw되는 예외를 처리하는 방법을 설명합니다. 이 섹션은 concurrency::p arallel_for와 같은 병렬 알고리즘에도 적용됩니다. 이러한 알고리즘은 작업 그룹을 기반으로 하기 때문입니다.

주의

예외가 종속 작업에 미치는 영향을 이해해야 합니다. 작업 또는 병렬 알고리즘에서 예외 처리를 사용하는 방법에 대한 권장 사례는 병렬 패턴 라이브러리 항목의 모범 사례에서 취소 및 예외 처리가 개체 파괴 에 미치는 영향 이해 섹션을 참조하세요.

작업 그룹에 대한 자세한 내용은 작업 병렬 처리를 참조하세요. 병렬 알고리즘에 대한 자세한 내용은 병렬 알고리즘을 참조 하세요.

동시성::task_group 또는 동시성::structured_task_group 개체에 전달하는 작업 함수 본문에서 예외를 throw하면 런타임은 해당 예외를 저장하고 동시성::task_group::wait, 동시성::structured_task_group::wait, 동시성::task_group::run_and_wait 또는 동시성을 호출하는 컨텍스트로 마샬링합니다. 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 = 30Caught exception: point is NULL.

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

[맨 위로 이동]

런타임에 의해 throw된 예외

예외는 런타임 호출로 인해 발생할 수 있습니다. 동시성::task_canceled 및 동시성::operation_timed_out 을 제외한 대부분의 예외 형식은 프로그래밍 오류를 나타냅니다. 이러한 오류는 일반적으로 복구할 수 없으므로 애플리케이션 코드에서 catch하거나 처리해서는 안 됩니다. 프로그래밍 오류를 진단해야 하는 경우 애플리케이션 코드에서 복구할 수 없는 오류만 catch하거나 처리하는 것이 좋습니다. 그러나 런타임에 정의된 예외 유형을 이해하면 프로그래밍 오류를 진단하는 데 도움이 될 수 있습니다.

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

다음 예제에서는 동시성::p arallel_invoke 알고리즘을 사용하여 두 작업을 병렬로 실행합니다. 첫 번째 작업은 5초 동안 기다린 다음 메시지 버퍼에 메시지를 보냅니다. 두 번째 작업은 함수를 receive 사용하여 동일한 메시지 버퍼에서 메시지를 수신하는 데 3초 정도 기다립니다. 함수는 receiveoperation_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;
   }  
}

다음은 이 예제에 대한 샘플 출력을 보여줍니다.

8293104567Caught exception: -5: the value is less than the minimum.

[맨 위로 이동]

취소

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

[맨 위로 이동]

간단한 작업

간단한 작업은 동시성::Scheduler 개체에서 직접 예약하는 작업입니다. 경량 작업은 일반 작업보다 오버헤드가 적습니다. 그러나 런타임은 간단한 작업에서 throw되는 예외를 catch하지 않습니다. 대신 처리되지 않은 예외 처리기가 예외를 catch합니다. 이 처리기는 기본적으로 프로세스를 종료합니다. 따라서 애플리케이션에서 적절한 오류 처리 메커니즘을 사용합니다. 간단한 작업에 대한 자세한 내용은 작업 스케줄러를 참조 하세요.

[맨 위로 이동]

비동기 에이전트

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

다음 예제에서는 동시성::agent에서 파생되는 클래스의 예외를 처리하는 한 가지 방법을 보여 줍니다. 이 예제에서는 클래스를 points_agent 정의합니다. 메서드 pointpoints_agent::run 메시지 버퍼에서 개체를 읽고 콘솔에 출력합니다. 메서드는 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 occurred 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 occurred 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 occurred in agent: point must not be NULL
the status of the agent is: done

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

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

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

[맨 위로 이동]

요약

[맨 위로 이동]

참고 항목

동시성 런타임
작업 병렬 처리
병렬 알고리즘
PPL에서의 취소
작업 Scheduler
비동기 에이전트