Abbruch in der PPL

In diesem Dokument wird die Rolle des Abbruchs in der Parallel Patterns Library (PPL) erläutert und beschrieben, wie Sie die parallele Arbeitsvorgänge abbrechen, und wie Sie erkennen können, ob parallele Arbeitsvorgänge abgebrochen wurden.

Hinweis

Die Laufzeit implementiert Abbrüche mithilfe der Ausnahmebehandlung. Diese Ausnahmen dürfen im eigenen Code nicht abgefangen oder behandelt werden. Außerdem empfiehlt es sich, ausnahmesicheren Code in den Funktionsrümpfen der Aufgaben zu schreiben. Sie können z. B. das RAII-Muster (Resource Acquisition Is Initialization ) verwenden, um sicherzustellen, dass Ressourcen korrekt behandelt werden, wenn eine Ausnahme im Textkörper eines Vorgangs ausgelöst wird. Ein vollständiges Beispiel, das das RAII-Muster verwendet, um eine Ressource in einem abbruchfähigen Vorgang sauber, finden Sie unter Walkthrough: Removing Work from a User-Interface Thread.

Die wichtigsten Punkte

  • Das Abbrechen ist ein kooperativer Vorgang und beinhaltet die Koordination zwischen dem Code, der den Abbruch verlangt, und der Aufgabe, die auf den Abbruch reagieren muss.

  • Verwenden Sie wenn möglich Abbruchtoken, um Arbeitsvorgänge abzubrechen. Die Parallelität::cancellation_token Klasse definiert ein Abbruchtoken.

  • Wenn Sie Abbruchtoken verwenden, verwenden Sie die Parallelität::cancellation_token_source::cancel-Methode , um den Abbruch zu initiieren, und die Parallelität::cancel_current_task-Funktion , um auf den Abbruch zu reagieren. Verwenden Sie die Parallelität::cancellation_token::is_canceled Methode, um zu überprüfen, ob eine andere Aufgabe den Abbruch angefordert hat.

  • Der Abbruch erfolgt nicht unmittelbar. Obwohl neue Arbeit nicht gestartet wird, wenn eine Aufgabe oder Aufgabengruppe abgebrochen wird, muss die aktive Arbeit auf den Abbruch prüfen und darauf reagieren.

  • Eine wertbasierte Fortsetzung erbt das Abbruchtoken ihrer Vorgängeraufgabe. Eine aufgabenbasierte Fortsetzung erbt das Token der Vorgängeraufgabe dagegen nicht.

  • Verwenden Sie die Parallelität::cancellation_token::none-Methode , wenn Sie einen Konstruktor oder eine Funktion aufrufen, der ein cancellation_token Objekt akzeptiert, aber nicht möchten, dass der Vorgang abgebrochen werden kann. Wenn Sie kein Abbruchtoken an den Parallelitätskonstruktor::task oder die Parallelität::create_task-Funktion übergeben, kann diese Aufgabe nicht abgebrochen werden.

In diesem Dokument

Parallele Arbeitsstrukturen

Differenzierte Aufgaben und Berechnungen werden in der PPL mithilfe von Aufgaben und Aufgabengruppen verwaltet. Sie können Aufgabengruppen verschachteln, um Strukturen paralleler Arbeit zu bilden. Die folgende Abbildung zeigt eine parallele Arbeitsstruktur. In dieser Abbildung stellen tg1 und tg2 Aufgabengruppen dar; t1, t2, t3, t4 und t5 stellen die Arbeitsvorgänge dar, die von den Aufgabengruppen durchgeführt wird.

A parallel work tree.

Das folgende Beispiel zeigt den Code, der zum Erstellen der Struktur in der Abbildung erforderlich ist. In diesem Beispiel sind parallele tg2 Objekte::structured_task_group Objekte; t1, , t2, t3, t4und sind t5Parallelität::task_handle-Objekte.tg1

// task-tree.cpp
// compile with: /c /EHsc
#include <ppl.h>
#include <sstream>
#include <iostream>
#include <sstream>

using namespace concurrency;
using namespace std;

void create_task_tree()
{   
   // Create a task group that serves as the root of the tree.
   structured_task_group tg1;

   // Create a task that contains a nested task group.
   auto t1 = make_task([&] {
      structured_task_group tg2;
      
      // Create a child task.
      auto t4 = make_task([&] {
         // TODO: Perform work here.
      });

      // Create a child task.
      auto t5 = make_task([&] {
         // TODO: Perform work here.
      });

      // Run the child tasks and wait for them to finish.
      tg2.run(t4);
      tg2.run(t5);
      tg2.wait();
   });

   // Create a child task.
   auto t2 = make_task([&] {
      // TODO: Perform work here.
   });

   // Create a child task.
   auto t3 = make_task([&] {
      // TODO: Perform work here.
   });

   // Run the child tasks and wait for them to finish.
   tg1.run(t1);
   tg1.run(t2);
   tg1.run(t3);
   tg1.wait();   
}

Sie können auch die Parallelität::task_group Klasse verwenden, um eine ähnliche Arbeitsstruktur zu erstellen. Die Parallelitätsklasse::task unterstützt auch den Begriff einer Arbeitsstruktur. Eine task-Struktur ist jedoch eine Abhängigkeitsstruktur. In einer task-Struktur werden zukünftige Arbeitsvorgänge nach aktuellen Arbeitsvorgängen abgeschlossen. In einer Aufgabengruppenstruktur werden interne Arbeitsvorgänge vor externen Arbeitsvorgängen abgeschlossen. Weitere Informationen zu den Unterschieden zwischen Aufgaben und Aufgabengruppen finden Sie unter Task Parallelism.

[Nach oben]

Abbrechen paralleler Vorgänge

Es gibt verschiedene Möglichkeiten, parallele Arbeitsvorgänge abzubrechen. Die bevorzugte Methode ist, ein Abbruchtoken zu verwenden. Aufgabengruppen unterstützen auch die Parallelität::task_group::cancel-Methode und die Parallelität::structured_task_group::cancel-Methode . Eine letzte Möglichkeit ist, im Text einer Arbeitsfunktion einer Aufgabe eine Ausnahme auszulösen. Unabhängig von der gewählten Methode sollten Sie bedenken, dass der Abbruch nicht sofort auftritt. Obwohl neue Arbeit nicht gestartet wird, wenn eine Aufgabe oder Aufgabengruppe abgebrochen wird, muss die aktive Arbeit auf den Abbruch prüfen und darauf reagieren.

Weitere Beispiele zum Abbrechen paralleler Aufgaben finden Sie unter Walkthrough: Verbinden ing Using Tasks and XML HTTP Requests, How to: Use Cancellation to Break from a Parallel Loop, and How to: Use Exception Handling to Break from a Parallel Loop.

Verwenden eines Abbruchtokens zum Abbrechen der parallelen Arbeit

Die Klassen task, task_group und structured_task_group unterstützen Abbruchvorgänge durch die Verwendung von Abbruchtoken. Die PPL definiert die Parallelität::cancellation_token_source und parallele Klassen::cancellation_token zu diesem Zweck. Wenn Sie Arbeit mithilfe eines Abbruchtokens abbrechen, wird von der Runtime keine neue Verarbeitung gestartet, die dieses Token abonniert. Arbeiten, die bereits aktiv sind, können die is_canceled Memberfunktion verwenden, um das Abbruchtoken zu überwachen und zu beenden, wenn dies möglich ist.

Rufen Sie zum Initiieren des Abbruchs die Parallelität::cancellation_token_source::cancel-Methode auf. Auf Abbrüche reagieren Sie folgendermaßen:

  • Verwenden Sie für task Objekte die Funktion "concurrency::cancel_current_task ". cancel_current_task bricht die aktuelle Aufgabe und alle wertbasierten Fortsetzungen ab. (Das Abbruchtoken, das der Aufgabe oder seinen Fortsetzungen zugeordnet ist, wird nicht abgebrochen.)

  • Verwenden Sie für Aufgabengruppen und parallele Algorithmen die Parallelität::is_current_task_group_canceling-Funktion , um Abbruch zu erkennen und so schnell wie möglich vom Aufgabentext zurückzugeben, wenn diese Funktion zurückgegeben wird true. (Verwenden Sie bei Aufgabengruppen nicht cancel_current_task.)

Das folgende Beispiel zeigt das erste grundlegende Muster für den Aufgabenabbruch. Der Aufgabentext überprüft die Schleife von Zeit zu Zeit auf Abbrüche.

// task-basic-cancellation.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <concrt.h>
#include <iostream>
#include <sstream>

using namespace concurrency;
using namespace std;

bool do_work()
{
    // Simulate work.
    wcout << L"Performing work..." << endl;
    wait(250);
    return true;
}

int wmain()
{
    cancellation_token_source cts;
    auto token = cts.get_token();

    wcout << L"Creating task..." << endl;

    // Create a task that performs work until it is canceled.
    auto t = create_task([&]
    {
        bool moreToDo = true;
        while (moreToDo)
        {
            // Check for cancellation.
            if (token.is_canceled())
            {
                // TODO: Perform any necessary cleanup here...

                // Cancel the current task.
                cancel_current_task();
            }
            else 
            {
                // Perform work.
                moreToDo = do_work();
            }
        }
    }, token);

    // Wait for one second and then cancel the task.
    wait(1000);

    wcout << L"Canceling task..." << endl;
    cts.cancel();

    // Wait for the task to cancel.
    wcout << L"Waiting for task to complete..." << endl;
    t.wait();

    wcout << L"Done." << endl;
}

/* Sample output:
    Creating task...
    Performing work...
    Performing work...
    Performing work...
    Performing work...
    Canceling task...
    Waiting for task to complete...
    Done.
*/

Die cancel_current_task-Funktion löst eine Ausnahme aus; daher müssen Sie nicht explizit von der aktuellen Schleife oder Funktion zurückkehren.

Tipp

Alternativ können Sie die Parallelität::interruption_point-Funktion anstelle von cancel_current_task.

Es ist wichtig, cancel_current_task als Reaktion auf einen Abbruch aufzurufen, da die Aufgabe dadurch in den abgebrochenen Zustand übergeht. Wenn Sie zu früh zurückkehren, anstatt cancel_current_task aufzurufen, geht der Vorgang in den abgeschlossenen Zustand über, und alle wertbasierten Fortsetzungen werden ausgeführt.

Achtung

Lösen Sie nie task_canceled in Ihrem Code aus. Rufen Sie stattdessen cancel_current_task auf.

Wenn eine Aufgabe im abgebrochenen Zustand endet, löst die Concurrency::task::get-Methode Parallelität::task_canceled aus. (Umgekehrt gibt die Parallelität::task::waittask_status::canceled zurück und wird nicht ausgelöst.) Im folgenden Beispiel wird dieses Verhalten für eine aufgabenbasierte Fortsetzung veranschaulicht. Eine aufgabenbasierte Fortsetzung wird immer aufgerufen, auch wenn die Vorgängeraufgabe abgebrochen wurde.

// task-canceled.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
    auto t1 = create_task([]() -> int
    {
        // Cancel the task.
        cancel_current_task();
    });

    // Create a continuation that retrieves the value from the previous.
    auto t2 = t1.then([](task<int> t)
    {
        try
        {
            int n = t.get();
            wcout << L"The previous task returned " << n << L'.' << endl;
        }
        catch (const task_canceled& e)
        {
            wcout << L"The previous task was canceled." << endl;
        }
    });

    // Wait for all tasks to complete.
    t2.wait();
}
/* Output:
    The previous task was canceled.
*/

Da wertbasierte Fortsetzungen das Token der Vorgängeraufgabe erben, wenn sie nicht mit einem expliziten Token erstellt wurden, gehen die Fortsetzungen sofort in den abgebrochenen Zustand über, auch wenn die Vorgängeraufgabe noch ausgeführt wird. Daher wird jede Ausnahme, die von der Vorgängeraufgabe nach dem Abbruch ausgelöst wird, nicht an die Fortsetzungsaufgaben weitergegeben. Der Abbruch überschreibt immer den Status der Vorgängeraufgabe. Das folgende Beispiel ähnelt dem vorhergehenden, veranschaulicht jedoch das Verhalten einer wertbasierten Fortsetzung.

auto t1 = create_task([]() -> int
{
    // Cancel the task.
    cancel_current_task();
});

// Create a continuation that retrieves the value from the previous.
auto t2 = t1.then([](int n)
{
    wcout << L"The previous task returned " << n << L'.' << endl;
});

try
{
    // Wait for all tasks to complete.
    t2.get();
}
catch (const task_canceled& e)
{
    wcout << L"The task was canceled." << endl;
}
/* Output:
    The task was canceled.
*/

Achtung

Wenn Sie kein Abbruchtoken an den task Konstruktor oder die Parallelität::create_task-Funktion übergeben, kann diese Aufgabe nicht abgebrochen werden. Außerdem müssen Sie an den Konstruktor geschachtelter Aufgaben – Aufgaben, die im Text einer anderen Aufgabe erstellt werden – das Abbruchtoken dieser Aufgabe übergeben, um alle Aufgaben gleichzeitig abzubrechen.

Sie haben die Möglichkeit, beliebigen Code auszuführen, wenn ein Abbruchtoken abgebrochen wird. Wenn Der Benutzer beispielsweise auf der Benutzeroberfläche eine Schaltfläche "Abbrechen " auswäht, um den Vorgang abzubrechen, können Sie diese Schaltfläche deaktivieren, bis der Benutzer einen anderen Vorgang startet. Das folgende Beispiel zeigt, wie Sie die Parallelität::cancellation_token::register_callback-Methode verwenden, um eine Rückruffunktion zu registrieren, die ausgeführt wird, wenn ein Abbruchtoken abgebrochen wird.

// task-cancellation-callback.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
    cancellation_token_source cts;
    auto token = cts.get_token();

    // An event that is set in the cancellation callback.
    event e;

    cancellation_token_registration cookie;
    cookie = token.register_callback([&e, token, &cookie]()
    {
        wcout << L"In cancellation callback..." << endl;
        e.set();

        // Although not required, demonstrate how to unregister 
        // the callback.
        token.deregister_callback(cookie);
    });

    wcout << L"Creating task..." << endl;

    // Create a task that waits to be canceled.
    auto t = create_task([&e]
    {
        e.wait();
    }, token);

    // Cancel the task.
    wcout << L"Canceling task..." << endl;
    cts.cancel();

    // Wait for the task to cancel.
    t.wait();

    wcout << L"Done." << endl;
}
/* Sample output:
    Creating task...
    Canceling task...
    In cancellation callback...
    Done.
*/

Im Dokument "Task Parallelism" wird der Unterschied zwischen wertbasierten und aufgabenbasierten Fortsetzungen erläutert. Wenn Sie kein cancellation_token-Objekt für eine Fortsetzungsaufgabe angeben, erbt die Fortsetzung das Abbruchtoken der Vorgängeraufgabe wie folgt:

  • Eine wertbasierte Fortsetzung erbt immer das Abbruchtoken der Vorgängeraufgabe.

  • Eine aufgabenbasierte Fortsetzung erbt dagegen das Abbruchtoken der Vorgängeraufgabe nicht. Die einzige Möglichkeit, eine aufgabenbasierte Fortsetzung abbrechbar zu machen, ist die explizite Übergabe eines Abbruchtokens.

Diese Verhalten werden nicht durch eine fehlerhafte Aufgabe beeinträchtigt (das heißt, eine Aufgabe, die eine Ausnahme auslöst). In diesem Fall wird eine wertbasierte Fortsetzung abgebrochen; Eine aufgabenbasierte Fortsetzung wird nicht abgebrochen.

Achtung

Eine Aufgabe, die in einer anderen Aufgabe erstellt wird (das heißt, eine geschachtelte Aufgabe) erbt nicht das Abbruchtoken der übergeordneten Aufgabe. Nur wertbasierte Fortsetzungen erben das Abbruchtoken ihrer Vorgängeraufgabe.

Tipp

Verwenden Sie die Parallelität::cancellation_token::none-Methode , wenn Sie einen Konstruktor oder eine Funktion aufrufen, der ein cancellation_token Objekt verwendet, und sie möchten nicht, dass der Vorgang abgebrochen werden kann.

Sie können auch ein Abbruchtoken für den Konstruktor eines task_group- oder structured_task_group-Objekts angeben. Ein wichtiger Aspekt hierfür ist, dass die untergeordneten Aufgabengruppen dieses Abbruchtoken erben. Ein Beispiel, das dieses Konzept veranschaulicht, indem die Parallelität::run_with_cancellation_token-Funktion zum Aufrufen parallel_forverwendet wird, finden Sie weiter unten in diesem Dokument unter "Abbrechen paralleler Algorithmen ".

[Nach oben]

Abbruchtoken und Aufgabenkomposition

Die Parallelität::when_all und parallele Funktionen::when_any können Ihnen beim Verfassen mehrerer Aufgaben helfen, allgemeine Muster zu implementieren. In diesem Abschnitt wird beschrieben, wie diese Funktionen mit Abbruchtoken verwendet werden können.

Wenn Sie ein Abbruchtoken entweder für die Funktion und when_any die when_all Funktion bereitstellen, wird diese Funktion nur abgebrochen, wenn dieses Abbruchtoken abgebrochen wird oder wenn eine der Teilnehmeraufgaben in einem abgebrochenen Zustand endet oder eine Ausnahme auslöst.

Die Funktion when_all erbt das Abbruchtoken von jeder Aufgabe, die zum Gesamtvorgang gehört, sofern Sie ihr kein Abbruchtoken übergeben. Die aufgabe, die when_all zurückgegeben wird, wird abgebrochen, wenn eines dieser Token abgebrochen wird und mindestens eine der Teilnehmeraufgaben noch nicht gestartet oder ausgeführt wird. Ein ähnliches Verhalten tritt auf, wenn eine der Aufgaben eine Ausnahme auslöst – die zurückgegebene when_all Aufgabe wird mit dieser Ausnahme sofort abgebrochen.

Die Runtime wählt das Abbruchtoken für die Aufgabe aus, das von der Funktion when_any zurückgegeben wird, wenn diese Aufgabe abgeschlossen ist. Wenn keine der beteiligten Aufgaben mit einem abgeschlossenen Zustand beendet wird und mindestens eine der Aufgaben eine Ausnahme auslöst, wird eine der auslösenden Aufgaben ausgewählt, um when_any zu beenden, und dessen Token wird als Token für die letzte Aufgabe ausgewählt. Wenn mehr als eine Aufgabe mit einem abgeschlossenen Zustand beendet wird, endet die Aufgabe, die von der Aufgabe when_any zurückgegeben wird, mit einem abgeschlossenen Zustand. Die Runtime versucht, eine abgeschlossene Aufgabe auszuwählen, deren Token zum Zeitpunkt des Abschlusses nicht abgebrochen ist, sodass die Aufgabe, die von when_any zurückgegeben wird, nicht sofort abgebrochen wird, auch wenn wenn andere ausgeführte Aufgaben möglicherweise zu einem späteren Zeitpunkt abgeschlossen werden.

[Nach oben]

Verwenden der Cancel-Methode zum Abbrechen der parallelen Arbeit

Die Parallelität::task_group::cancel und concurrency::structured_task_group::cancel-Methoden legen eine Aufgabengruppe auf den abgebrochenen Zustand fest. Nach dem Aufruf der cancel-Methode startet die Aufgabengruppe keine neuen Aufgaben mehr. Die cancel-Methoden können von mehreren untergeordneten Aufgaben aufgerufen werden. Eine abgebrochene Aufgabe bewirkt, dass die Parallelität::task_group::wait und concurrency::structured_task_group::wait-Methoden parallelcurrency::canceled zurückgeben.

Wenn eine Aufgabengruppe abgebrochen wird, können Aufrufe von jeder untergeordneten Aufgabe zur Laufzeit einen Unterbrechungspunkt auslösen, wodurch die Laufzeit einen internen Ausnahmetyp auslöst und abfangen kann, um aktive Aufgaben abzubrechen. Die Concurrency Runtime definiert keine bestimmten Unterbrechungspunkte. Diese können in jedem Aufruf der Runtime auftreten. Die Runtime muss die ausgelösten Ausnahmen behandeln, um den Abbruch durchzuführen. Behandeln Sie daher keine unbekannten Ausnahmen im Text einer Aufgabe.

Wenn eine untergeordnete Aufgabe einen zeitaufwändigen Vorgang ausführt und die Runtime nicht aufruft, muss sie regelmäßig nach einem Abbruch suchen und rechtzeitig beendet werden können. Das folgende Beispiel zeigt eine Möglichkeit zur Bestimmung des Abbruchzeitpunkts. Die Aufgabe t4 bricht die übergeordnete Aufgabengruppe ab, wenn sie auf einen Fehler stößt. Die Aufgabe t5 ruft regelmäßig die structured_task_group::is_canceling-Methode auf, um nach einem Abbruch zu suchen. Wenn die übergeordnete Aufgabengruppe abgebrochen wird, druckt die Aufgabe t5 eine Meldung und wird beendet.

structured_task_group tg2;

// Create a child task.
auto t4 = make_task([&] {
   // Perform work in a loop.
   for (int i = 0; i < 1000; ++i)
   {
      // Call a function to perform work.
      // If the work function fails, cancel the parent task
      // and break from the loop.
      bool succeeded = work(i);
      if (!succeeded)
      {
         tg2.cancel();
         break;
      }
   }
});

// Create a child task.
auto t5 = make_task([&] {
   // Perform work in a loop.
   for (int i = 0; i < 1000; ++i)
   {
      // To reduce overhead, occasionally check for 
      // cancelation.
      if ((i%100) == 0)
      {
         if (tg2.is_canceling())
         {
            wcout << L"The task was canceled." << endl;
            break;
         }
      }

      // TODO: Perform work here.
   }
});

// Run the child tasks and wait for them to finish.
tg2.run(t4);
tg2.run(t5);
tg2.wait();

In diesem Beispiel wird bei jeder 100. Iteration der Aufgabenschleife auf abgesagt. Die Häufigkeit, mit der nach einem Abbruch gesucht wird, hängt vom Arbeitsaufwand der Aufgabe ab und davon, wie schnell die Reaktion der Aufgaben auf den Abbruch sein soll.

Wenn Sie keinen Zugriff auf das übergeordnete Aufgabengruppenobjekt haben, rufen Sie die Parallelität::is_current_task_group_canceling-Funktion auf, um zu bestimmen, ob die übergeordnete Aufgabengruppe abgebrochen wird.

Die cancel-Methode wird nur auf die jeweils untergeordneten Aufgaben angewendet. Wenn Sie z. B. die Aufgabengruppe tg1 in der Abbildung der parallelen Arbeitsstruktur abbrechen, sind alle Aufgaben in der Struktur (t1, t2, t3, t4 und t5) betroffen. Wenn Sie die geschachtelte Aufgabengruppe tg2 abbrechen, sind dagegen nur die Aufgaben t4 und t5 betroffen.

Wenn Sie die cancel-Methode aufrufen, werden auch alle untergeordneten Aufgabengruppen abgebrochen. Dies gilt jedoch nicht für die übergeordneten Elemente der Aufgabengruppe in der parallelen Arbeitsstruktur. In den folgenden Beispielen wird dies auf Grundlage der Abbildung der parallelen Arbeitsstruktur veranschaulicht.

Im ersten Beispiel wird eine Arbeitsfunktion für die Aufgabe t4 erstellt, die der Arbeitsgruppe tg2 untergeordnet ist. Die Arbeitsfunktion ruft die Funktion work in einer Schleife auf. Wenn ein Aufruf von work fehlschlägt, wird die übergeordnete Aufgabengruppe der Aufgabe abgebrochen. Hierdurch geht die Aufgabengruppe tg2 in den Zustand "abgebrochen" über, die Aufgabengruppe tg1 wird jedoch nicht abgebrochen.

auto t4 = make_task([&] {
   // Perform work in a loop.
   for (int i = 0; i < 1000; ++i)
   {
      // Call a function to perform work.
      // If the work function fails, cancel the parent task
      // and break from the loop.
      bool succeeded = work(i);
      if (!succeeded)
      {
         tg2.cancel();
         break;
      }
   }         
});

Dieses zweite Beispiel ähnelt dem ersten, mit dem Unterschied, dass die Aufgabe hier die Aufgabengruppe tg1 abbricht. Dadurch sind alle Aufgaben in der Struktur betroffen (t1, t2, t3, t4 und t5).

auto t4 = make_task([&] {
   // Perform work in a loop.
   for (int i = 0; i < 1000; ++i)
   {
      // Call a function to perform work.
      // If the work function fails, cancel all tasks in the tree.
      bool succeeded = work(i);
      if (!succeeded)
      {
         tg1.cancel();
         break;
      }
   }   
});

Die structured_task_group-Klasse ist nicht threadsicher. Daher erzeugt eine untergeordnete Aufgabe, die eine Methode des übergeordneten structured_task_group-Objekts aufruft, ein nicht spezifiziertes Verhalten. Die Ausnahmen von dieser Regel sind die structured_task_group::cancel Methoden " parallel::structured_task_group::is_canceling ". Eine untergeordnete Aufgabe kann diese Methoden aufrufen, um die übergeordnete Aufgabengruppe abzubrechen und auf einen Abbruch zu prüfen.

Achtung

Sie können zwar ein Abbruchtoken verwenden, um Arbeitsvorgänge einer Aufgabengruppe abzubrechen, die als untergeordnetes Objekt eines task-Objekts ausgeführt werden, Sie können jedoch nicht die Methoden task_group::cancel oder structured_task_group::cancel verwenden, um task-Objekte abzubrechen, die in einer Aufgabengruppe ausgeführt werden.

[Nach oben]

Verwenden von Ausnahmen zum Abbrechen der parallelen Arbeit

Die Verwendung von Abbruchtoken und cancel-Methode ist effizienter als die Ausnahmebehandlung beim Abbrechen einer parallelen Arbeitsstruktur. Abbruchtoken und die cancel-Methode brechen eine Aufgabe und alle untergeordneten Aufgaben von oben nach unten ab (Top-Down-Ansatz). Bei der Ausnahmebehandlung wird dagegen die umgekehrte Reihenfolge verwendet (Bottom-Up-Ansatz), sodass jede untergeordnete Aufgabengruppe einzeln abgebrochen werden muss. Im Thema "Ausnahmebehandlung " wird erläutert, wie die Parallelitätslaufzeit Ausnahmen verwendet, um Fehler zu kommunizieren. Nicht alle Ausnahmen geben jedoch einen Fehler an. Ein Suchalgorithmus kann z. B. die zugeordnete Aufgabe abbrechen, wenn das Ergebnis gefunden wurde. Wie jedoch bereits erwähnt ist die Ausnahmebehandlung im Vergleich zur cancel-Methode die weniger effiziente Möglichkeit zum Abbrechen paralleler Aufgaben.

Achtung

Es wird empfohlen, dass Sie nur dann Ausnahmen verwenden, um parallele Arbeit abzubrechen, wenn dies notwendig ist. Abbruchtoken und die cancel-Methoden der Aufgabengruppen sind effizienter und weniger fehleranfällig.

Wenn Sie im Text einer Arbeitsfunktion, die Sie an eine Aufgabengruppe übergeben, eine Ausnahme auslösen, speichert die Runtime diese Ausnahme und marshallt sie an den Kontext, der auf das Beenden der Aufgabengruppe wartet. Wie auch bei der cancel-Methode verwirft die Runtime alle Aufgaben, die noch nicht gestartet wurden, und akzeptiert keine neuen Aufgaben.

Dieses dritte Beispiel ähnelt dem zweiten, mit dem Unterschied, dass die Aufgabe t4 eine Ausnahme auslöst, um die Aufgabengruppe tg2 abzubrechen. In diesem Beispiel wird ein try-catch Block verwendet, um den Abbruch zu überprüfen, wenn die Aufgabengruppe tg2 auf den Abschluss der untergeordneten Aufgaben wartet. Wie im ersten Beispiel geht die Aufgabengruppe tg2 in den Zustand „abgebrochen“ über, die Aufgabengruppe tg1 ist jedoch nicht betroffen.

structured_task_group tg2;

// Create a child task.      
auto t4 = make_task([&] {
   // Perform work in a loop.
   for (int i = 0; i < 1000; ++i)
   {
      // Call a function to perform work.
      // If the work function fails, throw an exception to 
      // cancel the parent task.
      bool succeeded = work(i);
      if (!succeeded)
      {
         throw exception("The task failed");
      }
   }         
});

// Create a child task.
auto t5 = make_task([&] {
   // TODO: Perform work here.
});

// Run the child tasks.
tg2.run(t4);
tg2.run(t5);

// Wait for the tasks to finish. The runtime marshals any exception
// that occurs to the call to wait.
try
{
   tg2.wait();
}
catch (const exception& e)
{
   wcout << e.what() << endl;
}

In diesem vierten Beispiel wird mithilfe der Ausnahmebehandlung die gesamte Arbeitsstruktur abgebrochen. Im Beispiel wird die Ausnahme abgefangen, wenn Aufgabengruppe tg1 auf das Beenden der untergeordneten Aufgaben wartet, nicht tg2. Wie im zweiten Beispiel gehen so beide Aufgabengruppen in der Struktur (tg1 und tg2) in den Zustand "abgebrochenen" über.

// Run the child tasks.
tg1.run(t1);
tg1.run(t2);
tg1.run(t3);   

// Wait for the tasks to finish. The runtime marshals any exception
// that occurs to the call to wait.
try
{
   tg1.wait();
}
catch (const exception& e)
{
   wcout << e.what() << endl;
}

Da die task_group::wait-Methode und die structured_task_group::wait-Methode ausgelöst werden, wenn eine untergeordnete Aufgabe eine Ausnahme auslöst, geben diese keinen Rückgabewert aus.

[Nach oben]

Abbrechen paralleler Algorithmen

Parallele Algorithmen in der PPL, z. B. parallel_for, basieren auf Aufgabengruppen. Daher können Sie die meisten Techniken für Aufgabengruppen auch zum Abbrechen paralleler Algorithmen verwenden.

In den folgenden Beispielen werden mehrere Möglichkeiten zum Abbrechen eines parallelen Algorithmus gezeigt.

Im folgenden Beispiel wird die run_with_cancellation_token-Funktion verwendet, um den parallel_for-Algorithmus aufzurufen. Die run_with_cancellation_token-Funktion nimmt ein Abbruchtoken als Argument und ruft die bereitgestellte Arbeitsfunktion synchron auf. Da parallele Algorithmen auf Aufgaben basieren, erben sie das Abbruchtoken der übergeordneten Aufgabe. Daher kann parallel_for auf den Abbruch reagieren.

// cancel-parallel-for.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>
#include <sstream>

using namespace concurrency;
using namespace std;

int wmain()
{
    // Call parallel_for in the context of a cancellation token.
    cancellation_token_source cts;
    run_with_cancellation_token([&cts]() 
    {
        // Print values to the console in parallel.
        parallel_for(0, 20, [&cts](int n)
        {
            // For demonstration, cancel the overall operation 
            // when n equals 11.
            if (n == 11)
            {
                cts.cancel();
            }
            // Otherwise, print the value.
            else
            {
                wstringstream ss;
                ss << n << endl;
                wcout << ss.str();
            }
        });
    }, cts.get_token());
}
/* Sample output:
    15
    16
    17
    10
    0
    18
    5
*/

Im folgenden Beispiel wird die Parallelität::structured_task_group::run_and_wait Methode verwendet, um den parallel_for Algorithmus aufzurufen. Die structured_task_group::run_and_wait-Methode wartet auf das Beenden der angegebenen Aufgabe. Das structured_task_group-Objekt aktiviert die Arbeitsfunktion zum Abbrechen der Aufgabe.

// To enable cancelation, call parallel_for in a task group.
structured_task_group tg;

task_group_status status = tg.run_and_wait([&] {
   parallel_for(0, 100, [&](int i) {
      // Cancel the task when i is 50.
      if (i == 50)
      {
         tg.cancel();
      }
      else
      {
         // TODO: Perform work here.
      }
   });
});

// Print the task group status.
wcout << L"The task group status is: ";
switch (status)
{
case not_complete:
   wcout << L"not complete." << endl;
   break;
case completed:
   wcout << L"completed." << endl;
   break;
case canceled:
   wcout << L"canceled." << endl;
   break;
default:
   wcout << L"unknown." << endl;
   break;
}

Folgende Ergebnisse werden zurückgegeben:

The task group status is: canceled.

Im folgenden Beispiel wird eine parallel_for-Schleife mithilfe der Ausnahmebehandlung abgebrochen. Die Runtime marshallt die Ausnahme an den aufrufenden Kontext.

try
{
   parallel_for(0, 100, [&](int i) {
      // Throw an exception to cancel the task when i is 50.
      if (i == 50)
      {
         throw i;
      }
      else
      {
         // TODO: Perform work here.
      }
   });
}
catch (int n)
{
   wcout << L"Caught " << n << endl;
}

Folgende Ergebnisse werden zurückgegeben:

Caught 50

Im folgenden Beispiel wird der Abbruch in einer parallel_for-Schleife mit einem booleschen Flag koordiniert. Jede Aufgabe wird ausgeführt, da in diesem Beispiel nicht die übergeordnete Aufgabengruppe mit der cancel-Methode oder mit Ausnahmebehandlung abgebrochen wird. Diese Methode kann daher mehr Rechenleistung erfordern als die anderen Methoden.

// Create a Boolean flag to coordinate cancelation.
bool canceled = false;

parallel_for(0, 100, [&](int i) {
   // For illustration, set the flag to cancel the task when i is 50.
   if (i == 50)
   {
      canceled = true;
   }

   // Perform work if the task is not canceled.
   if (!canceled)
   {
      // TODO: Perform work here.
   }
});

Jede Abbruchmethode hat andere Vorteile. Wählen Sie die Methode, die Ihren Anforderungen am besten entspricht.

[Nach oben]

Wann nicht der Abbruch verwendet werden soll

Die Verwendung eines Abbruchs ist sinnvoll, wenn jeder Member einer Gruppe zusammenhängender Aufgaben rechtzeitig beendet werden kann. In einigen Fällen ist ein Abbruch jedoch für die Anwendung nicht sinnvoll. Da der Aufgabenabbruch kooperativ ist, wird die übergeordnete Aufgabengruppe beispielsweise nicht abgebrochen, wenn eine einzelne Aufgabe blockiert wird. Wenn z. B. eine Aufgabe, mit der die Blockierung einer anderen aktiven Aufgabe aufgehoben wird, noch nicht gestartet wurde, wird diese bei Abbruch der Aufgabengruppe nicht gestartet. Dies kann zu einem Deadlock-Fehler in der Anwendung führen. Ein Abbruch ist ebenfalls nicht sinnvoll, wenn eine Aufgabe abgebrochen wird, die untergeordnete Aufgabe jedoch einen wichtigen Vorgang, z. B. das Freigeben einer Ressource, ausführt. Da mit dem Abbruch der übergeordneten Aufgabe der gesamte Satz von Aufgaben abgebrochen wird, wird der Vorgang nicht ausgeführt. Ein Beispiel, das diesen Punkt veranschaulicht, finden Sie im Abschnitt "Grundlegendes zur Abbruch- und Ausnahmebehandlung von Auswirkungen auf die Objektvernichtung " im Thema "Best Practices" im Thema "Parallel Patterns Library".

[Nach oben]

Titel Beschreibung
Vorgehensweise: Verwenden eines Abbruchs zum Verlassen einer Parallel-Schleife Zeigt, wie mit dem Abbrechen ein paralleler Suchalgorithmus implementiert wird.
Vorgehensweise: Verwenden der Ausnahmebehandlung zum Verlassen einer Parallel-Schleife Zeigt, wie mit der task_group-Klasse ein Suchalgorithmus für eine einfache Struktur geschrieben wird.
Ausnahmebehandlung Beschreibt, wie die Runtime Ausnahmen behandelt, die von Aufgabengruppen, einfachen Aufgaben und asynchronen Agents ausgelöst werden, und wie in Anwendungen auf Ausnahmen reagiert wird.
Task-Parallelität Beschreibt, wie sich Aufgaben und Aufgabengruppen zueinander verhalten und wie Sie unstrukturierte und strukturierte Parallelität in Ihren Anwendungen verwenden können.
Parallele Algorithmen Beschreibt die parallelen Algorithmen, die Auflistungen von Daten gleichzeitig verarbeiten.
Parallel Patterns Library (PPL) Eine Übersicht über die Parallel Patterns Library.

Verweis

task-Klasse (Concurrency Runtime)

cancellation_token_source-Klasse

cancellation_token-Klasse

task_group-Klasse

structured_task_group-Klasse

parallel_for-Funktion