并发运行时中的异常处理

更新:2011 年 3 月

并发运行时使用 C++ 异常处理来告知多种错误。 这些错误包括使用的运行时无效、诸如无法获取资源之类的运行时错误,以及发生在提供给任务组的工作函数中的错误。 本主题描述运行时如何处理任务组、轻量级任务和异步代理引发的异常,以及如何在应用程序中响应异常。

各节内容

  • 任务组和并行算法

  • 轻量级任务

  • 异步代理

  • 常见的异常

  • 摘要

任务组和并行算法

本节描述运行时如何处理任务组引发的异常。 本节还适用于 Concurrency::parallel_for 等并行算法,因为这些算法建立在任务组基础之上。

警告

确保您了解异常对于依赖任务的影响。 有关如何对任务或并行算法使用异常处理的推荐做法,请参见“并行模式库中的最佳做法”主题中的了解取消和异常处理如何影响对象销毁一节。

有关任务组的更多信息,请参见任务并行(并发运行时)。 有关并行算法的更多信息,请参见并行算法

工作函数引发的异常

工作函数是传递给运行时的 lambda 函数、函数对象或函数指针。 当您将工作函数传递给任务组时,运行时将根据单独的上下文运行该工作函数。

在传递给 Concurrency::task_groupConcurrency::structured_task_group 对象的工作函数的主体中引发异常时,运行时将存储该异常,并将其封送到调用 Concurrency::task_group::waitConcurrency::structured_task_group::waitConcurrency::task_group::run_and_waitConcurrency::structured_task_group::run_and_wait 的上下文。 运行时还将停止任务组中的所有活动任务(包括子任务组中的任务),并放弃任何尚未开始的任务。

下面的示例演示了引发异常的工作函数的基本结构。 该示例使用 task_group 对象并行打印两个 point 对象的值。 print_point 工作函数会将 point 对象的值打印到控制台。 如果输入值为 NULL,则该工作函数将引发异常。 运行时将存储此异常,并将其封送到调用 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.

有关在任务组中使用异常处理的完整示例,请参阅如何:使用异常处理中断 Parallel 循环

运行时引发的异常

除了工作函数会引发异常外,对运行时的调用也可能会产生异常。 运行时引发的大多数异常表示编程错误。 由于这些错误通常无法恢复,因此不应由应用程序代码来捕获或处理。 但是,了解运行时所定义的异常类型可以帮助您诊断编程错误。 常见的异常一节描述了常见的异常以及这些异常发生的条件。

对于运行时引发的异常以及工作函数引发的异常,异常处理机制是相同的。 例如,如果 Concurrency::receive 函数在指定的时间段内未接收消息,它将引发 operation_timed_out。 如果 receive 在您传递给任务组的工作函数中引发异常,则运行时将存储该异常,并将其封送到调用 task_group::waitstructured_task_group::waittask_group::run_and_waitstructured_task_group::run_and_wait 的上下文。

下面的示例使用 Concurrency::parallel_invoke 算法以并行方式运行两个任务。 第一个任务等待五秒钟,然后将消息发送到消息缓冲区。 第二个任务使用 receive 函数等待三秒钟,以从相同的消息缓冲区中接收消息。 如果 receive 函数在该时间段中未收到消息,则它将引发 operation_timed_out

// 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 算法将数字打印到控制台。 如果输入值小于某最小值或大于某最大值,则它将引发异常。 在本示例中,多个工作函数可能会引发异常。

// 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 对象计划的任务。 轻量级任务所需的开销比普通任务小。 但是,轻量级任务引发的异常并不是由运行时来捕获, 而是由未经处理的异常处理程序来捕获,默认情况下该程序会终止进程。 因此,请在应用程序中使用适当的错误处理机制。 有关轻量级任务的更多信息,请参见任务计划程序(并发运行时)

[转到页首]

异步代理

与轻量级任务相似,运行时不会管理异步代理引发的异常。

下面的示例演示一种处理派生自 Concurrency::agent 的类中异常的方法。 此示例定义了 points_agent 类。 points_agent::run 方法将读取消息缓冲区中的 point 对象,并将它们打印到控制台。 如果 run 方法收到 NULL 指针,则它将引发异常。

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 类。 有关这些消息块的更多信息,请参见异步消息块

有关异步代理的更多信息,请参见异步代理

[转到页首]

常见的异常

下表列出了并发运行时中的常见异常类,并提供了引发异常的条件。 大多数异常类型(operation_timed_outunsupported_os 除外)表示编程错误。 由于这些错误通常无法恢复,因此不应由应用程序代码来捕获或处理。 我们建议您在需要诊断编程错误时仅捕获或处理应用程序代码中不可恢复的错误。

异常类

Condition

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

当前操作系统不支持运行时。

[转到页首]

摘要

当任务引发异常时,运行时将保存该异常,并将其封送到等待任务组完成的上下文。 对于诸如轻量级任务和代理之类的组件,运行时不会为您管理异常。 在这些情况下,您必须实现自己的异常处理机制。

[转到页首]

请参见

概念

并发运行时

任务并行(并发运行时)

并行算法

PPL 中的取消操作

任务计划程序(并发运行时)

异步代理

修订记录

日期

修订记录

原因

2011 年 3 月

添加了有关异常可能影响依赖任务的警示。

信息补充。