Compartir a través de


Control de excepciones en el runtime de simultaneidad

El Runtime de simultaneidad usa el control de excepciones de C++ para comunicar muchas modalidades de errores. Entre estos errores se incluyen el uso no válido del runtime, errores en tiempo de ejecución tales como el que se produce al no poder adquirir un recurso, y errores que afectan a funciones de trabajo que el usuario proporciona a los grupos de tareas. En este tema se describe cómo controla el runtime las excepciones que producen los grupos de tareas, las tareas ligeras y los agentes asincrónicos, y cómo se responde a las excepciones en las aplicaciones.

Secciones

  • Grupos de tareas y algoritmos paralelos

  • Tareas ligeras

  • Agentes asincrónicos

  • Excepciones comunes

  • Resumen

Grupos de tareas y algoritmos paralelos

En esta sección se describe cómo controla el runtime las excepciones que producen los grupos de tareas. Esta sección también se aplica a algoritmos paralelos como Concurrency::parallel_for, puesto que estos algoritmos se basan en grupos de tareas.

Nota de precauciónPrecaución

Asegúrese de que entiende los efectos que tienen las excepciones sobre las tareas dependientes. Para conocer los procedimientos recomendados sobre cómo usar el control de excepciones con tareas o algoritmos paralelos, vea la sección Comprender cómo afectan la cancelación y el control de excepciones a la destrucción de objetos del tema Procedimientos recomendados de la Biblioteca de modelos de procesamiento paralelo.

Para obtener más información acerca de los grupos de tareas, vea Paralelismo de tareas (Runtime de simultaneidad). Para obtener más información acerca de los algoritmos paralelos, vea Algoritmos paralelos.

Excepciones que producen las funciones de trabajo

Una función de trabajo es una función lambda, un objeto de función o un puntero a función que se pasa al runtime. Al pasar una función de trabajo a un grupo de tareas, el runtime ejecuta esa función de trabajo en un contexto independiente.

Cuando se produce una excepción en el cuerpo de una función de trabajo que se pasa a un objeto Concurrency::task_group o Concurrency::structured_task_group, el runtime almacena esa excepción y realiza el cálculo de referencias en el contexto que llama a Concurrency::task_group::wait, Concurrency::structured_task_group::wait, Concurrency::task_group::run_and_wait o Concurrency::structured_task_group::run_and_wait. El runtime también detiene todas las tareas activas que están en el grupo de tareas (incluidas las que se encuentran en grupos de tareas secundarios) y descarta cualquier tarea que no se haya iniciado todavía.

En el siguiente ejemplo se muestra la estructura básica de una función de trabajo que produce una excepción. En el ejemplo se usa un objeto task_group para imprimir los valores de dos objetos point en paralelo. La función de trabajo print_point imprime los valores de un objeto point en la consola. La función de trabajo produce una excepción si el valor de entrada es NULL. El runtime almacena esta excepción y calcula las referencias en el contexto que llama a 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;
   }
}

Este ejemplo produce el siguiente resultado.

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

Para obtener un ejemplo completo donde se usa el control de excepciones en un grupo de tareas, vea Cómo: Usar el control de excepciones para interrumpir un bucle Parallel.

Excepciones que produce el runtime

Además de tener su origen en las funciones de trabajo, una excepción puede ser el resultado de una llamada al runtime. La mayoría de las excepciones que produce el runtime indican un error de programación. Normalmente, estos errores son irrecuperables y, por consiguiente, no se deben detectar ni administrar en el código de aplicación. Sin embargo, conocer los tipos de excepciones que define el runtime puede ayudar a diagnosticar los errores de programación. En la sección Excepciones comunes se describen las excepciones comunes y las condiciones en las que se producen.

El mecanismo de control de excepciones es el mismo para las excepciones que produce el runtime y las excepciones que producen las funciones de trabajo. Por ejemplo, la función Concurrency::receive produce la excepción operation_timed_out cuando no recibe un mensaje en el periodo de tiempo especificado. Si receive produce una excepción en una función de trabajo que se pasa a un grupo de tareas, el runtime almacena esa excepción y calcula las referencias en el contexto que llama a task_group::wait, structured_task_group::wait, task_group::run_and_wait o structured_task_group::run_and_wait.

En el ejemplo siguiente se usa el algoritmo Concurrency::parallel_invoke para ejecutar dos tareas en paralelo. La primera tarea espera cinco segundos y, a continuación, envía un mensaje a un búfer de mensajes. La segunda tarea usa la función receive para esperar tres segundos a recibir un mensaje desde el mismo búfer de mensajes. La función receive produce la excepción operation_timed_out si no recibe el mensaje en dicho período de tiempo.

// 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;
   }
}

Este ejemplo produce el siguiente resultado.

The operation timed out.

Para evitar la terminación anómala de la aplicación, asegúrese de que el código controla las excepciones cuando llama al runtime. Controle también las excepciones al llamar a código externo que use el Runtime de simultaneidad, por ejemplo, una biblioteca de otro fabricante.

Varias excepciones

Si una tarea o algoritmo paralelo recibe varias excepciones, el runtime calcula las referencias sólo de una de esas excepciones en el contexto de la llamada. El runtime no garantiza para qué excepción se calculan las referencias.

En el ejemplo siguiente se usa el algoritmo parallel_for para imprimir números en la consola. Produce una excepción si el valor de entrada es menor que un determinado valor mínimo o mayor que un determinado valor máximo. En este ejemplo, varias funciones de trabajo pueden producir una excepción.

// 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;
   }  
}

A continuación, se muestra la salida de este ejemplo.

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

Cancelación

No todas las excepciones indican un error. Por ejemplo, un algoritmo de búsqueda podría utilizar el control de excepciones para detener su tarea asociada cuando encuentra el resultado. Para obtener más información sobre cómo usar los mecanismos de cancelación en el código, vea Cancelación en la biblioteca PPL.

[Ir al principio]

Tareas ligeras

Una tarea ligera es una tarea que se programa directamente desde un objeto Concurrency::Scheduler. Las tareas ligeras implican una menor sobrecarga que las tareas ordinarias. Sin embargo, el runtime no detecta las excepciones producidas por las tareas ligeras. En su lugar, el controlador de excepciones no controladas, que de forma predeterminada finaliza el proceso, detecta la excepción. Por consiguiente, use un mecanismo de control de errores adecuado en su aplicación. Para obtener más información sobre las tareas ligeras, vea Programador de tareas (Runtime de simultaneidad).

[Ir al principio]

Agentes asincrónicos

Como sucede con las tareas ligeras, el runtime no controla las excepciones producidas por agentes asincrónicos.

En el ejemplo siguiente se muestra una manera de controlar excepciones en una clase que se deriva de Concurrency::agent. En este ejemplo se define la clase points_agent. El método points_agent::run lee los objetos point del búfer de mensajes y los imprime en la consola. El método run produce una excepción si recibe un puntero NULL.

El método run rodea todo el trabajo en un bloque try-catch. El bloque catch almacena la excepción en un búfer de mensajes. La aplicación comprueba si el agente encontró un error leyendo el búfer cuando el agente finaliza.

// 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;
}

Este ejemplo produce el siguiente resultado.

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

Dado que el bloque try-catch existe fuera del bucle while, el agente finaliza el procesamiento cuando encuentra el primer error. Si el bloque try-catch estuviese dentro del bucle while, el agente continuaría después de producirse el error.

En este ejemplo se almacenan las excepciones en un búfer de mensajes para que otro componente pueda supervisar los errores del agente mientras se ejecuta. En este ejemplo se usa un objeto Concurrency::single_assignment para almacenar el error. Cuando un agente controla varias excepciones, la clase single_assignment almacena únicamente el primer mensaje que se le pasa. Para almacenar solo la última excepción, use la clase Concurrency::overwrite_buffer. Para almacenar todas las excepciones, use la clase Concurrency::unbounded_buffer. Para obtener más información acerca de estos bloques de mensajes, vea Bloques de mensajes asincrónicos.

Para obtener más información sobre los agentes asincrónicos, vea Agentes asincrónicos.

[Ir al principio]

Excepciones comunes

En la siguiente tabla se enumeran las clases de excepciones comunes del Runtime de simultaneidad y se proporcionan las circunstancias en las que se producen las excepciones. La mayoría de los tipos de excepciones, salvo operation_timed_out y unsupported_os, indican un error de programación. Normalmente, estos errores son irrecuperables y, por consiguiente, no se deben detectar ni administrar en el código de aplicación. Sugerimos sólo detectar o controlar los errores irrecuperables en el código de aplicación si es necesario diagnosticar errores de programación.

Clase de excepción

Condition

bad_target

Se pasa un puntero no válido a un bloque de mensajes.

context_self_unblock

Un contexto ha intentado desbloquearse.

context_unblock_unbalanced

El runtime intentó desbloquear un contexto, pero ya está desbloqueado.

default_scheduler_exists

Se intentó establecer un programador como el predeterminado, pero ya existe uno.

improper_lock

Se adquirió un bloqueo incorrectamente.

improper_scheduler_attach

Se adjuntó un contexto varias veces al mismo programador.

improper_scheduler_detach

Un contexto administrado internamente por el runtime se desasoció de su programador, o bien el contexto no está adjunto a ningún programador.

improper_scheduler_reference

Un contexto incrementó el contador de referencia de un programador que se está cerrando y ese contexto no es interno al programador.

invalid_link_target

El mismo objeto se encuentra vinculado a un bloque de mensajes más de una vez.

invalid_multiple_scheduling

Una tarea que no se ha completado se ha programado más de una vez.

invalid_operation

El runtime ha realizado una operación no válida.

invalid_oversubscribe_operation

Se deshabilitó la suscripción excesiva cuando no estaba habilitada.

invalid_scheduler_policy_key

Se proporcionó una clave de directiva no válida a un objeto Concurrency::SchedulerPolicy.

invalid_scheduler_policy_thread_specification

Se ha especificado que un objeto SchedulerPolicy tenga un nivel de simultaneidad máximo que es inferior al nivel de simultaneidad mínimo.

invalid_scheduler_policy_value

Se proporcionó un valor de directiva no válido a un objeto SchedulerPolicy.

message_not_found

Un bloque de mensajes no pudo encontrar un mensaje solicitado.

missing_wait

Un objeto de grupo de tareas se destruyó antes de llamar al método Concurrency::task_group::wait o Concurrency::structured_task_group::wait.

nested_scheduler_missing_detach

Un programador anidado no se desasoció correctamente del elemento primario.

operation_timed_out

Una operación no se completó en el período de tiempo especificado.

scheduler_not_attached

Un contexto intentó desasociarse de su programador, pero el contexto no estaba adjunto a ningún programador.

scheduler_resource_allocation_error

El runtime no adquirió un recurso crítico; por ejemplo, un recurso proporcionado por el sistema operativo.

unsupported_os

El runtime no se admite en el sistema operativo actual.

[Ir al principio]

Resumen

Cuando una tarea produce una excepción, el runtime mantiene esa excepción y calcula las referencias en el contexto que espera a que finalice el grupo de tareas. En el caso de componentes tales como tareas ligeras y agentes, el runtime no administra las excepciones en nombre del usuario. En estos casos, debe implementar su propio mecanismo del control de excepciones.

[Ir al principio]

Vea también

Conceptos

Runtime de simultaneidad

Paralelismo de tareas (Runtime de simultaneidad)

Algoritmos paralelos

Cancelación en la biblioteca PPL

Programador de tareas (Runtime de simultaneidad)

Agentes asincrónicos

Historial de cambios

Fecha

Historial

Motivo

Marzo de 2011

Se ha agregado una nota de precaución sobre los efectos que las excepciones pueden tener sobre las tareas dependientes.

Mejora de la información.