Ausnahmebehandlung in der Concurrency Runtime

Die Concurrency Runtime übermittelt viele Arten von Fehlern mithilfe der C++-Ausnahmebehandlung. Zu diesen Fehlern zählen die falsche Verwendung der Runtime, Runtime-Fehler wie etwa das Nichtabrufen einer Ressource sowie Fehler, die in Arbeitsfunktionen auftreten, die Sie für Arbeitsgruppen bereitstellen. In diesem Thema wird beschrieben, wie die Runtime Ausnahmen behandelt, die von Aufgabengruppen, einfachen Aufgaben und asynchronen Agents ausgelöst werden, und wie auf Ausnahmen von den Anwendungen reagiert wird.

Abschnitte

  • Aufgabengruppen und parallele Algorithmen

  • Einfache Aufgaben

  • Asynchrone Agents

  • Allgemeine Ausnahmen

  • Zusammenfassung

Aufgabengruppen und parallele Algorithmen

In diesem Abschnitt wird beschrieben, wie die Runtime Ausnahmen behandelt, die von Aufgabengruppen ausgelöst werden. Dieser Abschnitt gilt auch für parallele Algorithmen, z. B. Concurrency::parallel_for, da diese Algorithmen auf Aufgabengruppen aufbauen.

Warnung

Es ist wichtig, dass Sie die Auswirkungen von Ausnahmen auf abhängige Aufgaben verstehen. Empfohlene Vorgehensweisen zum Verwenden der Ausnahmebehandlung bei Aufgaben oder parallelen Algorithmen finden Sie im entsprechenden Abschnitt der Parallel Patterns Library unter Informieren Sie sich über die Auswirkungen von Abbruch und Ausnahmebehandlung auf die Zerstörung von Objekten.

Weitere Informationen zu Aufgabengruppen finden Sie unter Aufgabenparallelität (Concurrency Runtime). Weitere Informationen zu parallelen Algorithmen finden Sie unter Parallele Algorithmen.

Von Arbeitsfunktionen ausgelöste Ausnahmen

Arbeitsfunktionen sind Lambda-Funktionen, Funktionsobjekte oder Funktionszeiger, die an die Runtime übergeben werden. Wenn eine Arbeitsfunktion an eine Aufgabengruppe übergeben wird, wird diese Arbeitsfunktion von der Runtime in einem separaten Kontext ausgeführt.

Wenn im Text einer Arbeitsfunktion, die an ein Concurrency::task_group-Objekt oder an ein Concurrency::structured_task_group-Objekt übergeben wird, eine Ausnahme ausgelöst wird, wird diese Ausnahme von der Runtime gespeichert und an den Kontext gemarshallt, der Concurrency::task_group::wait, Concurrency::structured_task_group::wait, Concurrency::task_group::run_and_wait oder Concurrency::structured_task_group::run_and_wait aufruft. Darüber hinaus werden von der Runtime auch alle aktiven Aufgaben in der Aufgabengruppe (sowie alle aktiven Aufgaben in untergeordneten Aufgabengruppen) beendet sowie alle noch nicht gestarteten Aufgaben verworfen.

Im folgenden Beispiel wird die grundlegende Struktur einer Arbeitsfunktion dargestellt, die eine Ausnahme auslöst. Im Beispiel werden die Werte von zwei point-Objekten mithilfe eines task_group-Objekts parallel ausgegeben. Die print_point-Arbeitsfunktion gibt die Werte eines point-Objekts an der Konsole aus. Die Arbeitsfunktion löst eine Ausnahme aus, wenn der Eingabewert NULL ist. Diese Ausnahme wird von der Runtime gespeichert und an den Kontext gemarshallt, der task_group::wait aufruft.

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

Folgende Ergebnisse werden zurückgegeben:

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

Ein umfassendes Beispiel für die Verwendung der Ausnahmebehandlung in einer Aufgabengruppe finden Sie unter Gewusst wie: Verwenden der Ausnahmebehandlung zum Verlassen einer Parallel-Schleife.

Von der Runtime ausgelöste Ausnahmen

Eine Ausnahme kann außer durch Arbeitsfunktionen auch durch den Aufruf der Runtime ausgelöst werden. Bei den meisten von der Runtime ausgelösten Ausnahmen handelt es sich um einen Programmierfehler. Diese Fehler sind in der Regel nicht behebbar und dürfen daher durch Anwendungscode weder abgefangen noch behandelt werden. Die von der Runtime definierten Ausnahmetypen zu kennen, erleichtert jedoch die Diagnose von Programmierfehlern. Im Abschnitt Allgemeine Ausnahmen werden die allgemeinen Ausnahmen sowie die Bedingungen beschrieben, unter denen diese auftreten.

Der Mechanismus für die Ausnahmenbehandlung ist bei Ausnahmen, die durch die Runtime ausgelöst werden, derselbe wie bei Ausnahmen, der durch Arbeitsfunktionen ausgelöst werden. Die Concurrency::receive-Funktion löst beispielsweise eine operation_timed_out-Ausnahme aus, wenn eine Nachricht nicht im angegebenen Zeitraum empfangen wird. Wenn receive eine Ausnahme in einer Arbeitsfunktion auslöst, die an eine Aufgabengruppe übergeben wird, wird diese Ausnahme von der Runtime gespeichert und an den Kontext gemarshallt, der task_group::wait, structured_task_group::wait, task_group::run_and_wait oder structured_task_group::run_and_wait aufruft.

Im folgenden Beispiel werden zwei Aufgaben mithilfe des Concurrency::parallel_invoke-Algorithmus parallel ausgeführt. Die erste Aufgabe wartet fünf Sekunden und sendet dann eine Nachricht an einen Nachrichtenpuffer. Die zweite Aufgabe wartet mithilfe der receive-Funktion drei Sekunden auf eine Nachricht vom selben Nachrichtenpuffer. Die receive-Funktion löst die operation_timed_out-Ausnahme aus, wenn die Nachricht nicht im angegebenen Zeitraum empfangen wird.

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

Folgende Ergebnisse werden zurückgegeben:

The operation timed out.

Um sicherzustellen, dass die Anwendung ordnungsgemäß beendet wird, muss der Code beim Aufrufen der Runtime Ausnahmen behandeln. Behandeln Sie auch Ausnahmen, wenn Sie externen Code wie etwa die Bibliothek eines Drittanbieters aufrufen, der die Concurrency Runtime verwendet.

Mehrere Ausnahmen

Wenn eine Aufgabe oder paralleler Algorithmus mehrere Ausnahmen empfängt, wird nur eine dieser Ausnahmen von der Runtime an den aufrufenden Kontext gemarshallt. Dabei kann nicht vorhergesagt werden, welche Ausnahme von der Runtime gemarshallt wird.

Im folgenden Beispiel werden mithilfe des parallel_for-Algorithmus Zahlen an der Konsole ausgegeben. Es wird eine Ausnahme ausgegeben, wenn der Eingabewert kleiner als ein Mindestwert oder größer als ein Maximalwert ist. In diesem Beispiel können mehrere Arbeitsfunktionen eine Ausnahme auslösen.

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

Nachfolgend wird eine Beispielausgabe für dieses Beispiel angezeigt.

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

Abbruch

Nicht alle Ausnahmen geben einen Fehler an. Ein Suchalgorithmus kann beispielsweise mithilfe der Ausnahmenbehandlung die zugehörige Aufgabe beenden, sobald das Ergebnis gefunden ist. Weitere Informationen zum Verwenden von Abbruchmechanismen im Code finden Sie unter Abbruch in der PPL.

[Nach oben]

Einfache Aufgaben

Eine einfache Aufgabe ist eine Aufgabe, die Sie direkt über ein Concurrency::Scheduler-Objekt planen. Einfache Aufgaben erfordern einen geringeren Aufwand als gewöhnliche Aufgaben. Die Runtime fängt jedoch keine Ausnahmen ab, die von einfachen Aufgaben ausgelöst werden. Stattdessen wird die Ausnahme vom Handler für nicht behandelte Ausnahmen abgefangen, der den Prozess standardmäßig beendet. Verwenden Sie daher in der Anwendung einen entsprechenden Fehlerbehandlungsmechanismus. Weitere Informationen zu einfachen Aufgaben finden Sie unter Taskplaner (Concurrency Runtime).

[Nach oben]

Asynchrone Agents

Die Runtime verwaltet wie bei einfachen Aufgaben auch keine Ausnahmen, die von asynchronen Agents ausgelöst werden.

Im folgenden Beispiel wird eine Möglichkeit für die Behandlung von Ausnahmen in einer Klasse dargestellt, die von Concurrency::agent abgeleitet wird. In diesem Beispiel wird die points_agent-Klasse definiert. Die points_agent::run-Methode liest point-Objekte aus dem Nachrichtenpuffer und gibt sie an der Konsole aus. Die run-Methode löst beim Empfang eines NULL-Zeigers eine Ausnahme aus.

Die run-Methode schließt alle Arbeiten in einen try-catch-Block ein. Die Ausnahme wird vom catch-Block in einem Nachrichtenpuffer gespeichert. Die Anwendung überprüft, ob im Agent ein Fehler aufgetreten ist. Hierzu wird dieser Puffer nach Abschluss des Agents gelesen.

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

Folgende Ergebnisse werden zurückgegeben:

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

Da sich der try-catch-Block außerhalb der while-Schleife befindet, beendet der Agent die Verarbeitung, sobald der erste Fehler auftritt. Wenn sich der try-catch-Block innerhalb der while-Schleife befände, würde der Agent die Verarbeitung nach dem Auftreten eines Fehlers fortsetzen.

In diesem Beispiel werden Ausnahmen in einem Nachrichtenpuffer gespeichert, damit eine andere Komponente den Agent beim Ausführen auf Fehler überwachen kann. In diesem Beispiel wird der Fehler mithilfe eines Concurrency::single_assignment-Objekts gespeichert. Wenn ein Agent mehrere Ausnahmen behandelt speichert die single_assignment-Klasse nur die erste Nachricht, die an sie übergeben wird. Wenn nur die letzte Ausnahme gespeichert werden soll, verwenden Sie die Concurrency::overwrite_buffer-Klasse. Um alle Ausnahmen zu speichern, verwenden Sie die Concurrency::unbounded_buffer-Klasse. Weitere Informationen zu diesen Nachrichtenblöcken finden Sie unter Asynchrone Nachrichtenblöcke.

Weitere Informationen zu asynchronen Agents finden Sie unter Asynchrone Agents.

[Nach oben]

Allgemeine Ausnahmen

In der folgenden Tabelle sind die allgemeinen Ausnahmeklassen in der Concurrency Runtime sowie die Bedingungen aufgeführt, unter der die Ausnahmen ausgelöst werden. Außer bei den Ausnahmen operation_timed_out und unsupported_os handelt es sich bei den meisten Ausnahmetypen um Programmierfehler. Diese Fehler sind in der Regel nicht behebbar und dürfen daher durch Anwendungscode weder abgefangen noch behandelt werden. Es wird empfohlen, bei der Diagnose von Programmierfehlern nur nicht behebbare Fehler im Anwendungscode abzufangen oder zu behandeln.

Ausnahmeklasse

Bedingung

bad_target

Es wurde ein ungültiger Zeiger an einen Nachrichtenblock übergeben.

context_self_unblock

Ein Kontext hat versucht, die eigene Blockierung aufzuheben.

context_unblock_unbalanced

Die Runtime hat versucht, die Blockierung eines Kontexts aufzuheben, dessen Blockierung jedoch bereits aufgehoben war.

default_scheduler_exists

Es wurde versucht, einen Planer als Standardplaner festzulegen, obwohl bereits ein Standardplaner vorhanden ist.

improper_lock

Eine Sperre wurde falsch abgerufen.

improper_scheduler_attach

Ein Kontext wurde mehrmals an den gleichen Planer angefügt.

improper_scheduler_detach

Ein von der Runtime intern verwalteter Kontext wurde vom zugehörigen Planer getrennt, oder der Kontext wurde an keinen Planer angefügt.

improper_scheduler_reference

Ein Kontext hat den Verweiszähler für einen Planer inkrementiert, der heruntergefahren wird, und dieser Kontext ist im Planer nicht enthalten.

invalid_link_target

Ein Objekt wurde mehr als einmal mit einem Nachrichtenblock verknüpft.

invalid_multiple_scheduling

Eine nicht abgeschlossene Aufgabe wurde mehr als einmal geplant.

invalid_operation

Die Runtime hat einen ungültigen Vorgang ausgeführt.

invalid_oversubscribe_operation

Überzeichnung wurde deaktiviert, als sie nicht aktiviert war.

invalid_scheduler_policy_key

Für ein Concurrency::SchedulerPolicy-Objekt wurde ein ungültiger Richtlinienschlüssel bereitgestellt.

invalid_scheduler_policy_thread_specification

Für ein SchedulerPolicy-Objekt wurde eine maximale Parallelitätsebene angegeben, die unter der minimalen Parallelitätsebene liegt.

invalid_scheduler_policy_value

Für SchedulerPolicy-Objekt wurde ein ungültiger Richtlinienwert bereitgestellt.

message_not_found

Ein Nachrichtenblock findet eine angeforderte Nachricht nicht.

missing_wait

Ein Aufgabengruppenobjekt wurde vor dem Aufruf der Concurrency::task_group::wait-Methode oder der Concurrency::structured_task_group::wait-Methode zerstört.

nested_scheduler_missing_detach

Ein geschachtelter Planer wurde vom übergeordneten Element nicht ordnungsgemäß getrennt.

operation_timed_out

Eine Vorgang wurde nicht im angegebenen Zeitraum abgeschlossen.

scheduler_not_attached

Es wurde versucht, einen Kontext vom zugehörigen Planer zu trennen, obwohl der Kontext an keinen Planer angefügt war.

scheduler_resource_allocation_error

Die Runtime hat eine wichtige Ressource, z. B. eine Ressource, die vom Betriebssystem bereitgestellt wird, nicht abgerufen.

unsupported_os

Die Runtime wird unter dem aktuellen Betriebssystem nicht unterstützt.

[Nach oben]

Zusammenfassung

Wenn eine Aufgabe eine Ausnahme auslöst, wird diese Ausnahme von der Runtime gespeichert und an den Kontext gemarshallt, der wartet, bis die Aufgabengruppe beendet wird. Bei Komponenten wie bei einfachen Aufgaben und Agents verwaltet die Runtime keine Ausnahmen. In diesen Fällen müssen Sie einen eigenen Mechanismus für die Ausnahmenbehandlung implementieren.

[Nach oben]

Siehe auch

Konzepte

Concurrency Runtime

Aufgabenparallelität (Concurrency Runtime)

Parallele Algorithmen

Abbruch in der PPL

Taskplaner (Concurrency Runtime)

Asynchrone Agents

Änderungsprotokoll

Datum

Versionsgeschichte

Grund

März 2011

Ein Warnhinweis zu den möglichen Auswirkungen von Ausnahmen auf abhängige Aufgaben wurde hinzugefügt.

Informationsergänzung.