Обработка исключений в среде выполнения с параллелизмом

Среда выполнения с параллелизмом использует обработку исключений С++ для сообщения о различных видах ошибок. Эти ошибки включают недопустимое использование среды выполнения, такие ошибки среды выполнения как сбой в получении ресурса, а также ошибки в рабочих функциях, предоставляемых группам задач. В этом разделе описывается обработка в среде выполнения исключений, созданных группами задач, упрощенными задачами и асинхронными агентами, а также реагирование на исключения в приложениях.

Подразделы

  • Группы задач и параллельные алгоритмы

  • Упрощенные задачи

  • Асинхронные агенты

  • Общие исключения

  • Сводка

Группы задач и параллельные алгоритмы

В этом подразделе описывается обработка исключений, созданных группами задач, в среде выполнения. Этот раздел также относится к таким параллельным алгоритмам, как Concurrency::parallel_for, так как эти алгоритмы используют группы задач.

Предупреждение

Обязательно разберитесь с влиянием исключений на зависимые задачи.Рекомендации по использованию обработки исключений с задачами или параллельными алгоритмами см. в подразделе Знайте, как отмена и обработка исключений влияет на деструкцию объектов раздела "Рекомендации по использованию библиотеки параллельных шаблонов".

Дополнительные сведения о группах задач см. в разделе Параллелизм задач (среда выполнения с параллелизмом). Дополнительные сведения об алгоритмах параллельной обработки см. в разделе Параллельные алгоритмы.

Исключения, создаваемые рабочими функциями

Рабочая функция — это лямбда-функция, объект функции или указатель функции, передаваемый среде выполнения. При передаче рабочей функции группе задач среда выполнения запускает эту рабочую функцию в отдельном контексте.

При создании исключения в основной части рабочей функции, передаваемой объекту Concurrency::task_group или Concurrency::structured_task_group, среда выполнения хранит это исключение и маршалирует его в контекст, вызывающий метод Concurrency::task_group::wait, Concurrency::structured_task_group::wait, Concurrency::task_group::run_and_wait или Concurrency::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.

Полный пример, в котором используется обработка исключений в группе задач, см. в разделе Практическое руководство. Использование обработки исключений для выхода из параллельного цикла.

Исключения, создаваемые средой выполнения

Помимо рабочих функций исключение может создаваться в результате вызова среды выполнения. Большинство исключений, создаваемых средой выполнения, указывают на ошибку программирования. Как правило, это неустранимые ошибки, которые не должны перехватываться или обрабатываться кодом приложения. Однако понимание типов исключений, определенных средой выполнения, поможет в выявлении ошибок программирования. В подразделе Общие исключения описаны типичные исключения и условия их возникновения.

Механизмы обработки исключений, созданных средой выполнения и рабочими функциями, совпадают. Например, функция Concurrency::receive создает исключение operation_timed_out, если не получает сообщение в заданный период времени. Если функция receive создает исключение в рабочей функции, переданной группе задач, среда выполнения хранит это исключение и маршалирует его в контекст, вызывающий метод task_group::wait, structured_task_group::wait, task_group::run_and_wait или structured_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.

Отмена

Не все исключения указывают на ошибку. Например, алгоритм поиска может использовать обработку исключений для остановки связанной задачи при нахождении результата. Дополнительные сведения об использовании механизмов отмены в коде см. в разделе Отмена в библиотеке параллельных шаблонов.

[в начало]

Упрощенные задачи

Упрощенная задача — это задача, планируемая непосредственно из объекта 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_out и unsupported_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

Среда выполнения не поддерживается в текущей операционной системе.

[в начало]

Сводка

Когда задача создает исключение, среда выполнения хранит это исключение и маршалирует его в контекст, ожидающий завершения группы задач. Для некоторых компонентов (например, упрощенных задач и агентов) среда выполнения не управляет исключениями за пользователя. В этих случаях необходимо реализовывать собственный механизм обработки исключений.

[в начало]

См. также

Основные понятия

Среда выполнения с параллелизмом

Параллелизм задач (среда выполнения с параллелизмом)

Параллельные алгоритмы

Отмена в библиотеке параллельных шаблонов

Планировщик задач (среда выполнения с параллелизмом)

Асинхронные агенты

Журнал изменений

Дата

Журнал

Причина

Март 2011

Добавлено предупреждение о влиянии исключений на зависимые задачи.

Улучшение информации.