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 incorrecte de l'exécution, des erreurs d'exécution telles que l'échec d'acquérir une ressource, et les erreurs qui se produisent dans les fonctions de travail que vous fournissez aux tâches et aux groupes de tâches.Lorsqu'une tâche ou un groupe de tâches lève une exception, le conserve de runtime qui exception et la marshale au contexte qui attend la tâche ou 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.Cette rubrique décrit comment le runtime gère les exceptions levées par des tâches, des groupes de tâches, des tâches légères, et des agents asynchrones, et comment répondre aux exceptions dans vos applications.

Points clés

  • Lorsqu'une tâche ou un groupe de tâches lève une exception, le conserve de runtime qui exception et la marshale au contexte qui attend la tâche ou le groupe de tâches se termine.

  • Si possible, entourez chaque appel à concurrency::task::get et à concurrency::task::wait avec un bloc d' try/catch des erreurs de handle dont vous pouvez récupérer.Le runtime arrête l'application si une tâche lève une exception et cette exception n'est pas interceptée par la tâche, une de ses continuations, ou l'application principale.

  • Exécute tâche- basés d'une suite toujours ; elle n'importe pas si l'antécédent s'est terminée avec succès, est levé une exception, ou a été annulée.Une suite valeur- basée ne fonctionne pas si l'antécédent de tâche lève ou annule les.

  • Étant donné que l'exécution tâche- basé de suites toujours considèrent, si ajouter une suite tâche- basée à la fin de la chaîne de suite.Cela peut contribuer à garantir que votre code observe toutes les exceptions.

  • Le runtime lève concurrency::task_canceled lorsque vous appelez concurrency::task::get et cette tâche est annulée.

  • Le runtime ne gère pas les exceptions pour des tâches légères et des agents.

Dans ce document

  • Tâches et suites

  • Groupes de tâches et algorithmes parallèles

  • Exceptions levées par le runtime

  • Exceptions multiples

  • Annulation

  • Tâches légères

  • Agents asynchrones

Tâches et suites

Cette section décrit comment le runtime gère les exceptions levées par des objets de concurrency::task et leurs suites.Pour plus d'informations sur la tâche et le modèle de suite, consultez Parallélisme des tâches (runtime d'accès concurrentiel).

Lorsque vous levez une exception dans le corps d'une fonction de travail que vous passez à un objet d' task , aux runtime stocke cette exception et la marshale au contexte qui appelle concurrency::task::get ou concurrency::task::wait.Le document Parallélisme des tâches (runtime d'accès concurrentiel) décrit tâche- basé et des suites valeur- basées, mais pour résumer, une suite valeur- basée prend un paramètre de type T et une suite tâche- basée prend un paramètre de type task<T>.Si une tâche qui lève un ou plusieurs suites valeur- basées, ces suites ne sont pas planifiées pour fonctionner.L'exemple suivant illustre ce comportement :

// 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.
*/

Une suite tâche- basée vous permet de gérer toute exception qui est levée par l'antécédent.Exécute tâche- basés d'une suite toujours ; elle n'importe pas si la tâche terminée avec succès, est levé une exception, ou a été annulée.Lorsqu'une tâche lève une exception, ses continuations tâche- applications sont planifiées pour fonctionner.L'exemple suivant montre une tâche qui lève toujours.La tâche a deux suites ; il valeur- est basé et l'autre tâche- est basé.Passe tâche- basés d'exception toujours, et peuvent donc intercepter l'exception levée par l'antécédent.Lorsque l'exemple nécessite les deux suites pour terminer, l'exception est levée de nouveau parce que l'exception de tâche est toujours levée lorsque task::get ou task::wait est appelé.

// 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.
*/

Nous vous conseillons d'utiliser des suites tâche- sur pour intercepter des exceptions que vous pouvez gérer.Étant donné que l'exécution tâche- basé de suites toujours considèrent, si ajouter une suite tâche- basée à la fin de la chaîne de suite.Cela peut contribuer à garantir que votre code observe toutes les exceptions.l'exemple suivant montre une chaîne valeur-basée de base de suite.La troisième tâche dans lève de chaîne, et par conséquent pas de suite valeur- sur la qui suivent ne sont pas exécutées.Toutefois, la continuation finale tâche- est basée, et donc toujours s'exécute.Cette suite dernière gère l'exception levée par la troisième tâche.

Nous recommandons que vous interceptez les exceptions les plus spécifiques que vous pouvez.Vous pouvez omettre cette suite tâche- basée sur une dernière si vous n'avez pas d'exceptions spécifiques à intercepter.Toute exception reste non prise en charge et peut exécuter l'application.

// 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 value-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>
*/
ConseilConseil

Vous pouvez utiliser la méthode de concurrency::task_completion_event::set_exception pour associer une exception à un événement d'achèvement de tâche.Le document Parallélisme des tâches (runtime d'accès concurrentiel) décrit la classe de concurrency::task_completion_event plus en détail.

concurrency::task_canceled est un type d'exception runtime important qui associe à task.Le runtime lève task_canceled lorsque vous appelez task::get et cette tâche est annulée.(En revanche, task::wait retourne task_status::canceled et ne lève pas.) Vous pouvez intercepter et gérer cette exception à une suite tâche- basée ou lorsque vous appelez task::get.Pour plus d'informations sur l'annulation de tâche, consultez Annulation dans la bibliothèque de modèles parallèles.

Mise en gardeAttention

Ne levez jamais task_canceled de votre code.Appel concurrency::cancel_current_task à la place.

Le runtime arrête l'application si une tâche lève une exception et cette exception n'est pas interceptée par la tâche, une de ses continuations, ou l'application principale.Si votre application tombe en panne, vous pouvez configurer Visual Studio pour arrêter l'exécution lorsque des exceptions C++ sont levées.Après avoir diagnostiquiez l'emplacement de l'exception non gérée, utilisez une suite tâche- base pour la gérer.

La section Exceptions levées par le runtime dans ce document décrit comment travailler avec des exceptions runtime plus en détail.

[Supérieur]

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.

Mise en gardeAttention

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 Understand how Cancellation and Exception Handling Affect Object Destruction 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.

Lorsque vous levez une exception dans le corps d'une fonction de travail que vous passez à un objet de concurrency::task_group ou de concurrency::structured_task_group , aux 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.

[Supérieur]

Exceptions levées par le runtime

Une exception peut résulter d'un appel au runtime.La plupart des types d'exceptions, à l'exception concurrency::task_canceled et concurrency::operation_timed_out, 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.Toutefois, la connaissance des types d'exceptions définis par le runtime peut vous aider à diagnostiquer les erreurs de programmation.

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 de concurrency::receive lève operation_timed_out lorsqu'elle ne reçoit pas de message au cours de 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 de concurrency::parallel_invoke pour effectuer 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.

[Supérieur]

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.

[Supérieur]

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.

[Supérieur]

Tâches légères

Une tâche légère est une tâche que vous planifiez directement à partir d'un objet de 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).

[Supérieur]

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

Cet exemple génère la sortie suivante.

X: 10 Y: 20
X: 20 Y: 30
error occurred 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 de concurrency::single_assignment pour signaler 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 de concurrency::overwrite_buffer .Pour enregistrer toutes les exceptions, utilisez la classe de 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.

[Supérieur]

Résumé

[Supérieur]

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