Partager via


Gestion des exceptions dans le runtime d'accès concurrentiel

Le runtime d'accès concurrentiel utilise la gestion des exceptions C++ pour communiquer de nombreux genres d'erreurs. Ces erreurs incluent l'utilisation non valide du runtime, des erreurs d'exécution telles que l'échec d'acquisition d'une ressource et les erreurs qui se produisent dans les fonctions de travail que vous fournissez aux groupes de tâches. Cette rubrique décrit comment le runtime gère les exceptions levées par des groupes de tâches, des tâches légères et des agents asynchrones, et comment répondre aux exceptions dans vos applications.

Sections

  • Groupes de tâches et algorithmes parallèles

  • Tâches légères

  • Agents asynchrones

  • Exceptions courantes

  • Résumé

Groupes de tâches et algorithmes parallèles

Cette section décrit comment le runtime gère les exceptions levées par des groupes de tâches. Cette section s'applique également aux algorithmes parallèles tels que Concurrency::parallel_for, car ces algorithmes reposent sur les groupes de tâches.

Avertissement

Assurez-vous de comprendre les effets des exceptions sur les tâches dépendantes. Pour obtenir les méthodes recommandées pour utiliser la gestion des exceptions avec les tâches ou les algorithmes parallèles, consultez la section Comprendre comment l'annulation et la gestion des exceptions affectent la destruction d'objet des meilleures pratiques de la rubrique relative à la bibliothèque de modèles parallèles (PPL).

Pour plus d'informations sur les groupes de tâches, consultez Parallélisme des tâches (runtime d'accès concurrentiel). Pour plus d'informations sur les algorithmes parallèles, consultez Algorithmes parallèles.

Exceptions levées par les fonctions de travail

Une fonction de travail est une fonction lambda, un objet de fonction ou un pointeur fonction passé au runtime. Lorsque vous passez une fonction de travail à un groupe de tâches, le runtime exécute cette fonction de travail sur un contexte séparé.

Lorsque vous levez une exception dans le corps d'une fonction de travail que vous passez à un objet Concurrency::task_group ou Concurrency::structured_task_group, le runtime stocke cette exception et la marshale au contexte qui appelle Concurrency::task_group::wait, Concurrency::structured_task_group::wait, Concurrency::task_group::run_and_wait ou Concurrency::structured_task_group::run_and_wait. Le runtime arrête également toutes les tâches actives qui sont dans le groupe de tâches (y compris celles des groupes de tâches enfants) et ignore toutes les tâches qui n'ont pas encore démarré.

L'exemple suivant montre la structure de base d'une fonction de travail qui lève une exception. L'exemple utilise un objet task_group pour imprimer les valeurs de deux objets point en parallèle. La fonction de travail print_point imprime les valeurs d'un objet point sur la console. La fonction de travail lève une exception si la valeur d'entrée est NULL. Le runtime stocke cette exception et la marshale au contexte qui appelle 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;
   }
}

Cet exemple génère la sortie suivante :

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

Pour obtenir un exemple complet qui utilise la gestion des exceptions dans un groupe de tâches, consultez Comment : utiliser la gestion des exceptions pour rompre une boucle parallèle.

Exceptions levées par le runtime

En plus des fonctions de travail, une exception peut résulter d'un appel au runtime. La plupart des exceptions levées par le runtime indiquent une erreur de programmation. Ces erreurs sont en général irrécupérables, et par conséquent ne doivent être ni interceptées ni gérées par le code d'application. Toutefois, la connaissance des types d'exceptions définis par le runtime peut vous aider à diagnostiquer les erreurs de programmation. La section Exceptions courantes décrit les exceptions courantes et les conditions dans lesquelles elles se produisent.

Le mécanisme de gestion des exceptions est identique pour les exceptions levées par le runtime et pour celles levées par des fonctions de travail. Par exemple, la fonction Concurrency::receive lève operation_timed_out lorsqu'elle ne reçoit pas de message durant la période spécifiée. Si receive lève une exception dans une fonction de travail que vous passez à un groupe de tâches, le runtime stocke cette exception et la marshale au contexte qui appelle task_group::wait, structured_task_group::wait, task_group::run_and_wait ou structured_task_group::run_and_wait.

L'exemple suivant utilise l'algorithme Concurrency::parallel_invoke pour exécuter deux tâches en parallèle. La première tâche attend cinq secondes, puis envoie un message vers un tampon de messages. La deuxième tâche utilise la fonction receive pour attendre pendant trois secondes de recevoir un message du même tampon de messages. La fonction receive lève operation_timed_out si elle ne reçoit pas le message dans le délai imparti.

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

Cet exemple génère la sortie suivante :

The operation timed out.

Pour empêcher tout arrêt anormal de votre application, assurez-vous que votre code gère les exceptions lorsqu'il fait appel au runtime. Gérez également les exceptions lorsque vous appelez du code externe qui utilise le runtime d'accès concurrentiel, par exemple une bibliothèque tierce.

Exceptions multiples

Si une tâche ou un algorithme parallèle reçoit plusieurs exceptions, le runtime marshale une seule de ces exceptions au contexte appelant. Le runtime ne garantit pas quelle exception il marshale.

L'exemple suivant utilise l'algorithme parallel_for pour imprimer des nombres sur la console. Il lève une exception si la valeur d'entrée est inférieure à une certaine valeur minimale ou supérieure à une certaine valeur maximale. Dans cet exemple, plusieurs fonctions de travail peuvent lever une exception.

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

Voici un exemple de sortie pour cet exemple.

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

Annulation

Les exceptions n'indiquent pas toutes une erreur. Par exemple, un algorithme de recherche peut utiliser la gestion des exceptions pour arrêter sa tâche associée lorsqu'il trouve le résultat. Pour plus d'informations sur la façon d'utiliser des mécanismes d'annulation dans votre code, consultez Annulation dans la bibliothèque de modèles parallèles.

[retour en haut]

Tâches légères

Une tâche légère est une tâche que vous planifiez directement à partir d'un objet Concurrency::Scheduler. Les tâches légères présentent des charges inférieures aux tâches ordinaires. En revanche, le runtime n'intercepte pas les exceptions levées par des tâches légères. Au lieu de cela, l'exception est interceptée par le gestionnaire d'exceptions non gérée, qui par défaut met fin au processus. Par conséquent, vous devez utiliser un mécanisme de gestion des erreurs approprié dans votre application. Pour plus d'informations sur les tâches légères, consultez Planificateur de tâches (runtime d'accès concurrentiel).

[retour en haut]

Agents asynchrones

Comme les tâches légères, le runtime ne gère pas les exceptions levées par les agents asynchrones.

L'exemple suivant illustre une manière de gérer les exceptions dans une classe qui dérive de Concurrency::agent. Cet exemple définit la classe points_agent. La méthode points_agent::run lit les objets point à partir du tampon de messages et les imprime sur la console. La méthode run lève une exception si elle reçoit un pointeur NULL.

La méthode run entoure tout le travail dans un bloc try-catch. Le bloc catch stocke l'exception dans un tampon de messages. L'application vérifie si l'agent a rencontré une erreur en lisant cette mémoire tampon une fois que l'agent a terminé.

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

Cet exemple génère la sortie suivante :

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

Étant donné que le bloc try-catch existe à l'extérieur de la boucle while, l'agent met fin au traitement lorsqu'il rencontre la première erreur. Si le bloc try-catch se trouvait à l'intérieur de la boucle while, l'agent continuerait après qu'une erreur se soit produite.

Cet exemple stocke les exceptions dans un tampon de messages afin qu'un autre composant puisse surveiller la présence éventuelle d'erreurs sur l'agent lors de son exécution. Cet exemple utilise un objet Concurrency::single_assignment pour stocker l'erreur. Dans le cas où un agent gère plusieurs exceptions, la classe single_assignment stocke uniquement le premier message qui lui est passé. Pour stocker uniquement la dernière exception, utilisez la classe Concurrency::overwrite_buffer. Pour stocker toutes les exceptions, utilisez la classe Concurrency::unbounded_buffer. Pour plus d'informations sur ces blocs de messages, consultez Blocs de messages asynchrones.

Pour plus d'informations sur les agents asynchrones, consultez Agents asynchrones.

[retour en haut]

Exceptions courantes

Le tableau suivant répertorie les classes d'exception courantes dans le runtime d'accès concurrentiel et indique la condition dans laquelle l'exception est levée. La plupart des types d'exceptions, à l'exception d' operation_timed_out et d' unsupported_os, indiquent une erreur de programmation. Ces erreurs sont en général irrécupérables, et par conséquent ne doivent être ni interceptées ni gérées par le code d'application. Nous vous suggérons d'intercepter ou de gérer les erreurs irrécupérables dans votre code d'application uniquement lorsque vous devez diagnostiquer des erreurs de programmation.

Classe d'exceptions

Condition

bad_target

Vous passez un pointeur non valide à un bloc de message.

context_self_unblock

Un contexte a essayé de se débloquer.

context_unblock_unbalanced

Le runtime a essayé de débloquer un contexte, mais ce contexte est déjà débloqué.

default_scheduler_exists

Une tentative de définition d'un planificateur comme planificateur par défaut a été effectuée, mais il existe déjà un planificateur par défaut.

improper_lock

Un verrou a été acquis de manière incorrecte.

improper_scheduler_attach

Un contexte a été attaché plusieurs fois au même planificateur.

improper_scheduler_detach

Un contexte géré en interne par le runtime a été détaché de son planificateur, ou le contexte n'est attaché à aucun planificateur.

improper_scheduler_reference

Un contexte a incrémenté le compteur de référence sur un planificateur qui s'arrête, et ce contexte n'est pas interne au planificateur.

invalid_link_target

Le même objet est lié à un bloc de message plusieurs fois.

invalid_multiple_scheduling

Une tâche qui ne s'est pas terminée a été planifiée à plusieurs reprises.

invalid_operation

Le runtime a effectué une opération non valide.

invalid_oversubscribe_operation

Le surabonnement a été désactivé alors qu'il n'était pas activé.

invalid_scheduler_policy_key

Une clé de stratégie non valide a été fournie à un objet Concurrency::SchedulerPolicy.

invalid_scheduler_policy_thread_specification

Un objet SchedulerPolicy est spécifié de façon à avoir un niveau d'accès concurrentiel maximal inférieur au niveau d'accès concurrentiel minimal.

invalid_scheduler_policy_value

Une valeur de stratégie non valide a été fournie à un objet SchedulerPolicy.

message_not_found

Un bloc de message n'a pas pu trouver un message demandé.

missing_wait

Un objet groupe de tâches a été détruit avant que la méthode Concurrency::task_group::wait ou Concurrency::structured_task_group::wait ait été appelée.

nested_scheduler_missing_detach

Un planificateur imbriqué ne s'est pas détaché correctement du parent.

operation_timed_out

Une opération ne s'est pas terminée dans le délai imparti.

scheduler_not_attached

Un contexte a tenté de se détacher de son planificateur alors qu'il n'était attaché à aucun planificateur.

scheduler_resource_allocation_error

Le runtime n'a pas acquis une ressource critique, par exemple une ressource fournie par le système d'exploitation.

unsupported_os

Le runtime n'est pas pris en charge sur le système d'exploitation actuel.

[retour en haut]

Résumé

Lorsqu'une tâche lève une exception, le runtime conserve cette exception et la marshale au contexte qui attend que le groupe de tâches se termine. Pour des composants tels que les tâches légères et les agents, le runtime ne gère pas les exceptions pour vous. Dans ces cas-là, vous devez implémenter votre propre mécanisme de gestion des exceptions.

[retour en haut]

Voir aussi

Concepts

Concurrency Runtime

Parallélisme des tâches (runtime d'accès concurrentiel)

Algorithmes parallèles

Annulation dans la bibliothèque de modèles parallèles

Planificateur de tâches (runtime d'accès concurrentiel)

Agents asynchrones

Historique des modifications

Date

Historique

Motif

Mars 2011

Ajout d'une note d'avertissement concernant les effets que les exceptions peuvent avoir sur les tâches dépendantes.

Améliorations apportées aux informations.