Freigeben über


Abbruch in der PPL

Dieses Dokument beschreibt die Rolle des Abbruchs in der Parallel Patterns Library (PPL), wie parallele Verarbeitung abgebrochen wird und wie bestimmt, wann Ähnlichkeitsarbeit abgebrochen wird.

HinweisHinweis

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.Zum Beispiel können Sie das RAII (Resource Acquisition Is Initialization)-Muster verwenden, um sicherzustellen, dass Ressourcen ordnungsgemäß behandelt werden, wenn eine Ausnahme im Rumpf einer Aufgabe ausgelöst wird.Ein vollständiges Beispiel, in dem das RAII-Muster verwendet wird, um in einer abbrechbaren Aufgabe eine Ressource zu bereinigen, finden Sie unter Exemplarische Vorgehensweise: Entfernen von Arbeit aus einem Benutzeroberflächenthread.

Punkte

  • Abbruch ist kooperativ und umfasst Koordination zwischen Code mit ein, der Abbruch und die Aufgabe erforderlich sind, die auf den Abbruch reagieren.

  • Wenn möglich, Verwendungsabbruchtoken, um die Verarbeitung abzubrechen.Die concurrency::cancellation_token-Klasse definiert ein Abbruchtoken.

  • Wenn Sie Abbruchtoken verwenden, verwenden Sie die Methode, um concurrency::cancellation_token_source::cancel Abbruch und die concurrency::is_task_cancellation_requested und concurrency::cancel_current_task-Funktionen zu initiieren, um auf den Abbruch reagieren.

  • Abbruch tritt nicht unmittelbar.Obwohl neues Werk nicht gestartet wird, wenn eine Aufgabe oder eine Aufgabengruppe abgebrochen wird, muss aktive Arbeit für überprüfen und auf den Abbruch reagieren.

  • Eine Fortsetzung wertbasierte erbt das Abbruchtoken der Vorgängeraufgabe.Eine Fortsetzung aufgabenbasierte erbt nie das Token der Vorgängeraufgabe.

  • Verwenden Sie die concurrency::cancellation_token::none-Methode, wenn Sie einen Konstruktor aufrufen oder arbeiten, der ein cancellation_token-Objekt akzeptiert, möchten Sie jedoch den Vorgang annullierbar nicht sein.Wenn Sie ein Abbruchtoken nicht zum concurrency::task-Konstruktor oder zur concurrency::create_task-Funktion übergeben, ist diese Aufgabe nicht annullierbar.

In diesem Dokument

  • Parallele Arbeitsstrukturen

  • Abbrechen von parallelen Aufgaben

    • Verwenden eines Abbruchtoken, zum Abbrechen paralleler Aufgaben

    • Abbrechen paralleler Aufgaben mit der cancel-Methode

    • Abbrechen paralleler Aufgaben mit Ausnahmen

  • Abbrechen von parallelen Algorithmen

  • Wann ein Abbruch nicht verwendet werden sollte

Parallele Arbeitsstrukturen

Die PPL verwendet Aufgaben und Aufgabengruppen, differenzierte Aufgaben und Berechnungen zu verwalten.Sie können Aufgabengruppen schachteln, um Strukturen paralleler Arbeitsvorgänge zu bilden.Die folgende Abbildung zeigt eine parallele Arbeitsstruktur.In dieser Abbildung dargestellt tg1 und tg2 Aufgabengruppen; t1, t2, t3, t4 und t5 stellen die Arbeiten, die die Aufgabengruppen ausführen.

Parallele Arbeitsstruktur

Das folgende Beispiel zeigt den Code, der zum Erstellen der Struktur in der Abbildung erforderlich ist.In diesem Beispiel sind tg1 und tg2concurrency::structured_task_group-Objekte; t1, t2, t3, t4 und t5 sind concurrency::task_handle-Objekte.

// 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 die concurrency::task_group-Klasse verwenden, um eine ähnliche Arbeitsstruktur zu erstellen.Die concurrency::task-Klasse unterstützt auch den Begriff einer Arbeitsstruktur.Es ist eine task-Struktur ein Abhängigkeitsbaum.In einer task-Struktur schließt zukünftige Arbeiten nach aktueller Arbeit ab.In einer Aufgabengruppenstruktur schließt interne Arbeit vor äußerer Arbeit ab.Weitere Informationen über die Unterschiede zwischen Aufgaben und Aufgabengruppen, finden Sie unter Aufgabenparallelität (Concurrency Runtime).

Anfang[]

Abbrechen von parallelen Aufgaben

Es gibt mehrere Möglichkeiten, parallele Arbeitsvorgänge abzubrechen.Die bevorzugte Methode besteht darin, ein Abbruchtoken zu verwenden.Aufgabengruppen unterstützen auch concurrency::task_group::cancel-Methode und die Methode concurrency::structured_task_group::cancel.Die fertige Methode ist, eine Ausnahme im Text einer Oder auszulösen.Unabhängig davon, dem Sie Methode auswählen, verstehen, dass Abbruch nicht sofort auftritt.Obwohl neues Werk nicht gestartet wird, wenn eine Aufgabe oder eine Aufgabengruppe abgebrochen wird, muss aktive Arbeit für überprüfen und auf den Abbruch reagieren.

Weitere Beispiele, die parallele Aufgaben abbrechen, finden Sie unter Exemplarische Vorgehensweise: Verbinden von Verwendungsaufgaben und XML-HTTP-Anforderung (IXHR2), Gewusst wie: Verwenden eines Abbruchs zum Verlassen einer Parallel-Schleife und Gewusst wie: Verwenden der Ausnahmebehandlung zum Verlassen einer Parallel-Schleife.

Dd984117.collapse_all(de-de,VS.110).gifVerwenden eines Abbruchtoken, zum Abbrechen paralleler Aufgaben

task, task_group und structured_task_group-Klassen unterstützen einen Abbruch durch die Verwendung von Abbruchtoken.Die PPL definiert die concurrency::cancellation_token_source und concurrency::cancellation_token-Klassen zu diesem Zweck.Wenn Sie ein Abbruchtoken verwenden, um Aufgaben mit, beginnt die Laufzeit nicht neues bearbeiten, die zu diesem Token abonniert.Arbeiten Sie, das bereits kann sein Abbruchtokens überwachen und beenden aktiv ist, wenn es kann.

Um den Abbruch zu initiieren, rufen Sie die Methode concurrency::cancellation_token_source::cancel an.Sie reagieren auf Abbrüche sind folgende:

  • Für task-Objekte verwenden Sie die concurrency::is_task_cancellation_requested und concurrency::cancel_current_task-Funktionen.cancel_current_task bricht die aktuelle Aufgabe und ihre wertbasierten Fortsetzungen ab.(Es wird nicht das Abbruch-Token abgebrochen, das der Aufgabe oder deren Fortsetzungen zugeordnet ist.)

  • Für Aufgabengruppen und parallele Algorithmen verwenden Sie die concurrency::is_current_task_group_canceling-Funktion, um den Abbruch zu erkennen und vom Aufgabentext so schnell wie möglich zurückzukehren, wenn diese Funktion true zurückgibt.Rufen Sie nicht cancel_current_task (von einer Aufgabengruppe an.)

Im folgenden Beispiel wird das erste grundlegende Muster für den Aufgabenabbruch gezeigt.Die prüft das Aufgabentexts gelegentlich für Abbrüche in eine Schleife.

// task-basic-cancellation.cpp
// compile with: /EHsc
#include <ppltasks.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 (is_task_cancellation_requested())
            {
                // 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 die - Ausnahme aus; Deshalb müssen Sie nicht, um von der TTY-Schnittstelle oder der Funktion explizit zurückzukehren.

TippTipp

Alternativ können Sie die concurrency::interruption_point-Funktion anstelle is_task_cancellation_requested und cancel_current_task aufrufen.

Es ist wichtig, cancel_current_task aufzurufen, wenn Sie auf einen Abbruch da geht die Aufgabe in den Zustand reagieren.Wenn Sie früh zurückkehren, anstatt, cancel_current_task aufzurufen, werden die Vorgangsübergänge in den Zustand und alle wertbasierten Fortsetzungen ausgeführt.

WarnhinweisVorsicht

Lösen Sie nie task_canceled aus dem Code aus.Aufruf cancel_current_task stattdessen.

Wenn Taskenden im abgebrochenen Zustand, die concurrency::task::get-Methode concurrency::task_canceled auslöst.(Umgekehrt, gibt concurrency::task::waittask_status::canceled zurück und löst nicht aus.) Das folgende Beispiel veranschaulicht dieses Verhalten für eine Fortsetzung aufgabenbasierte.Eine aufgabenbasierte Fortsetzung wird immer aufgerufen, wenn die Vorgängeraufgabe abgebrochen wird.

// 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, es sei denn, sie mit einem expliziten Token erstellt wurden, geben die Fortsetzungen sofort den Zustand ein, wenn die Vorgängeraufgabe noch ausgeführt wird.Daher weist jede Ausnahme, die von der Vorgängeraufgabe wird ausgelöst, nachdem ein Abbruch nicht zur Fortsetzung weitergegeben ist eine Arbeit zu.Abbruch überschreibt immer den Zustand der Vorgängeraufgabe.Das folgende Beispiel ähnelt dem vorherigen, aber veranschaulicht das Verhalten für eine wertbasierte 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.
*/
WarnhinweisVorsicht

Wenn Sie ein Abbruchtoken nicht zum task-Konstruktor oder zur concurrency::create_task-Funktion übergeben, ist diese Aufgabe nicht annullierbar.Außerdem müssen Sie das gleiche Abbruchtoken an den Konstruktor aller geschachtelten Aufgaben (das heißt, Aufgaben, die im Text einer anderen Aufgabe erstellt werden), führen alle Aufgaben gleichzeitig abzubrechen.

Sie sollten beliebigen Code ausführen, wenn ein Abbruchtoken abgebrochen wird.Wenn der Benutzer eine Schaltfläche Abbrechen auf der Benutzeroberfläche beschreibt, um die Operation abzubrechen, könnten Sie diese Schaltfläche deaktivieren, bis der Benutzer einen anderen Vorgang gestartet wird.Im folgenden Beispiel wird gezeigt, wie die concurrency::cancellation_token::register_callback-Methode verwendet, 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.
*/

Das Dokument Aufgabenparallelität (Concurrency Runtime) wird der Unterschied zwischen den wertbasierten und aufgabenbasierte Fortsetzungen.Wenn Sie kein cancellation_token-Objekt einer Fortsetzungsaufgabe bereitstellen, erbt die Fortsetzung das Abbruchtoken der vorherigen Aufgabe folgendermaßen:

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

  • Eine Fortsetzung aufgabenbasierte erbt nie das Abbruchtoken der Vorgängeraufgabe.Die einzige Möglichkeit, eine Fortsetzung aufgabenbasierte abbrechbar auszuführen ist, ein Abbruchtoken explizit zu übergeben.

Dieses Verhalten ist nicht durch eine bemängelte Aufgabe betroffen sind (das heißt, eine, die eine Ausnahme auslöst).In diesem Fall wird eine wertbasierte Fortsetzung abgebrochen; aufgabenbasierte eine Fortsetzung wird nicht abgebrochen.

WarnhinweisVorsicht

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

TippTipp

Verwenden Sie die concurrency::cancellation_token::none-Methode, wenn Sie einen Konstruktor aufrufen oder arbeiten, der ein cancellation_token-Objekt akzeptiert und den gewünschten Vorgang annullierbar nicht sein.

Sie können ein Abbruchtoken an den Konstruktor eines task_group oder structured_task_group-Objekts angeben.Ein wichtiger Aspekt hierfür ist, dass untergeordneten Aufgabengruppen dieses Abbruchtoken erben.Ein Beispiel dafür dieses Konzept, indem die concurrency::run_with_cancellation_token-Funktion verwendet, um ausgeführt werden, um parallel_for aufzurufen, finden Abbrechen von parallelen Algorithmen weiter unten in diesem Dokument.

Anfang[]

Dd984117.collapse_all(de-de,VS.110).gifAbbruchtoken und Aufgaben-Komposition

Die concurrency::when_all und concurrency::when_any-Funktionen können Ihnen helfen, mehrere Aufgaben zusammensetzt, allgemeine Muster zu implementieren.In diesem Abschnitt wird beschrieben, wie diese Funktionen mit Abbruchtoken arbeiten.

Wenn Sie ein Abbruchtoken an jedem die when_all und when_any-Funktion bereitstellen, Löschen dieser Funktion nur, wenn dieses Abbruchtoken abgebrochen wird, oder wenn eines der TeilnehmerTaskenden in einem abgebrochenen Zustand oder eine Ausnahme auslöst.

Die Funktion when_all erbt das Abbruchtoken von jeder Aufgabe, die der Gesamtvorgang erstellt, wenn Sie ein Abbruchtoken nicht darauf bereitstellen.Die Aufgabe, die von when_all zurückgegeben wird, wird, wenn alle Token abgebrochen werden und mindestens eines des Teilnehmers abgebrochen, den Aufgaben noch nicht gestartet wurde oder ausgeführt wird.Ein Ähnliches Verhalten tritt auf, wenn eine der Aufgaben eine Ausnahme auslöst - die Aufgabe, die von when_all zurückgegeben wird, wird sofort mit dieser Ausnahme abgebrochen.

Die Laufzeit wählt das Abbruchtoken für die Aufgabe aus, die von when_any-Funktion zurückgegeben wird, wenn diese Aufgabe ausführt.Wenn keines des Teilnehmeraufgabenendes in einem abgeschlossenen Zustand und eine oder mehrere der Aufgaben eine Ausnahme auslöst, wird eine der Aufgaben, die auslösen, ausgewählt, um when_any und zugehörigen Tokens abzuschließen wird die Option als das Token für die letzte Aufgabe.Wenn mehr als einen Ende mit Aufgaben im abgeschlossenen Zustand, der Aufgabe, die von den when_any Taskenden in einem abgeschlossenen Zustand zurückgegeben wird.Die Ablaufversuche, um einer abgeschlossenen Aufgabe auszuwählen, deren Token nicht zum Zeitpunkt der Vervollständigung für die Aufgabe abgebrochen wird, die von when_any zurückgegeben wird, wird nicht sofort abgebrochen, obwohl andere ausgeführte Aufgaben möglicherweise zu einem späteren Zeitpunkt abschlössen.

Anfang[]

Dd984117.collapse_all(de-de,VS.110).gifAbbrechen paralleler Aufgaben mit der cancel-Methode

Die concurrency::task_group::cancel und concurrency::structured_task_group::cancel-Methoden legen eine Aufgabengruppe in den 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 wird die concurrency::task_group::wait und concurrency::structured_task_group::wait-Methoden, concurrency::canceled zurückzugeben.

Wenn eine Aufgabengruppe abgebrochen wird, kann jeder Laufzeitaufruf durch eine untergeordnete Aufgabe einen Unterbrechungspunkt auslösen, der die Runtime veranlasst, einen internen Ausnahmetyp zum Abbrechen aktiver Aufgaben auszulösen und abzufangen.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 jedem hundertsten Durchlauf der Aufgabenschleife nach einem Abbruchvorgang gesucht.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 die übergeordnete Aufgabengruppe haben, rufen Sie die concurrency::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 oben 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 structured_task_group::cancel und die concurrency::structured_task_group::is_canceling-Methoden.Eine untergeordnete Aufgabe kann diese Methoden aufrufen, um die Gruppe und die Überprüfung für Abbruch der übergeordneten Aufgabe abzubrechen.

WarnhinweisVorsicht

Obwohl Sie ein Abbruchtoken verwenden können, um die Arbeit abzubrechen, die von einer Aufgabengruppe ausgeführt wird, die als untergeordnetes Element eines Objekts task ausgeführt wird, können Sie die task_group::cancel oder structured_task_group::cancel-Methoden nicht verwenden, um task-Objekte abzubrechen, die in einer Aufgabengruppe ausgeführt werden.

Anfang[]

Dd984117.collapse_all(de-de,VS.110).gifAbbrechen paralleler Aufgaben mit Ausnahmen

Die Verwendung von Abbruchtoken und die cancel-Methode sind effizienter als die Ausnahmebehandlung Abbrechen einer parallelen Arbeitsstruktur.Abbruchtoken und die cancel-Methode brechen eine Aufgabe und alle Aufgaben des untergeordneten Elements in einer von oben nach unten ab.Bei der Ausnahmebehandlung wird dagegen die umgekehrte Reihenfolge verwendet (Bottom-Up-Ansatz), sodass jede untergeordnete Aufgabengruppe einzeln abgebrochen werden muss.Im Thema Ausnahmebehandlung in der Concurrency Runtime wird erläutert, wie die Concurrency Runtime über Ausnahmen Fehler meldet.Nicht alle Ausnahmen geben jedoch einen Fehler an.Beispielsweise kann möglicherweise ein Suchalgorithmus die zugehörige Aufgabe ab, wenn das Ergebnis gefunden.Wie jedoch bereits erwähnt ist die Ausnahmebehandlung im Vergleich zur cancel-Methode die weniger effiziente Möglichkeit zum Abbrechen paralleler Aufgaben.

WarnhinweisVorsicht

Es wird empfohlen, dass Sie Ausnahmen verwenden, um parallele Verarbeitung nur dann abzubrechen.Abbruchtoken und die cancel-Methoden der Aufgabengruppe 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 nach einem Abbruch zu suchen, wenn die Aufgabengruppe tg2 auf das Beenden 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.

Anfang[]

Abbrechen von parallelen Algorithmen

Parallele Algorithmen in der PPL beispielsweise 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, um den parallel_for Algorithmus aufzurufen.Die run_with_cancellation_token-Funktion nimmt ein Abbruchtoken als Argument und ruft die bereitgestellte Arbeitsfunktion synchron an.Da parallele Algorithmen nach Aufgaben erstellt werden, 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 concurrency::structured_task_group::run_and_wait-Methode, 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.

Anfang[]

Wann ein Abbruch nicht verwendet werden sollte

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, in dem dieser Aspekt veranschaulicht wird, finden Sie im Abschnitt Understand how Cancellation and Exception Handling Affect Object Destruction der empfohlenen Vorgehensweisen im Thema zur Parallel Patterns Library.

Anfang[]

Verwandte Themen

Titel

Description

Gewusst wie: Verwenden eines Abbruchs zum Verlassen einer Parallel-Schleife

Zeigt, wie mit dem Abbrechen ein paralleler Suchalgorithmus implementiert wird.

Gewusst wie: Verwenden der Ausnahmebehandlung zum Verlassen einer Parallel-Schleife

Zeigt, wie die task_group-Klasse verwendet, um ein Suchalgorithmus für eine einfache Baumstruktur geschrieben.

Ausnahmebehandlung in der Concurrency Runtime

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.

Aufgabenparallelität (Concurrency Runtime)

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.

Verweise

task-Klasse (Concurrency Runtime)

cancellation_token_source-Klasse

cancellation_token-Klasse

task_group-Klasse

structured_task_group-Klasse

parallel_for-Funktion