Aufgabenparallelität (Concurrency Runtime)

In diesem Dokument wird die Rolle von Aufgaben und Aufgabengruppen in der Concurrency Runtime beschrieben.Eine Aufgabe ist eine Arbeitseinheit, die einen bestimmten Auftrag ausführt.Eine Aufgabe wird normalerweise parallel zu anderen Aufgaben ausgeführt und kann in weitere zerlegt werden, differenziertere, Aufgaben.Eine Reihe von Aufgaben wird in einer Aufgabengruppe organisiert.

Verwenden Sie Aufgaben, wenn Sie asynchronen Code schreiben und eine Operation auftreten soll, nach Abschluss der asynchronen Operation.Beispielsweise können Sie eine Aufgabe, einer Datei asynchron zu lesen und eine Fortsetzungsaufgabe, die weiter unten in diesem Dokument erläutert wird, die Daten zu verarbeiten, nachdem sie verfügbar ist.Umgekehrt VerwendungsArbeitsgruppen, um paralleler Arbeitsvorgänge in kleinere Stücke zu zerlegen.Nehmen Sie zum Beispiel einmal an, dass Sie über einen rekursiven Algorithmus verfügen, der die verbleibende Arbeit in zwei Partitionen unterteilt.Sie können Aufgabengruppen verwenden, um diese Partitionen gleichzeitig ausführen und warten dann auf die Arbeit geteilt, um abzuschließen.

TippTipp

Wenn Sie die gleiche Routine parallel auf jedes Element einer Auflistung anwenden möchten, verwenden Sie einen parallelen Algorithmus, z concurrency::parallel_for, statt einer Aufgabe oder einer Aufgabengruppe.Weitere Informationen zu parallelen Algorithmen finden Sie unter Parallele Algorithmen.

Punkte

  • Wenn Sie Variablen als Verweis an einen Lambda-Ausdruck übergeben, müssen Sie sicherstellen, dass die Lebensdauer dieser Variablen bis zum Beenden der Aufgabe erhalten bleibt.

  • Verwenden Sie die concurrency::taskAufgaben (- Klasse) Wenn Sie asynchronen Code schreiben.

  • Verwenden Sie Aufgabengruppen (wie die concurrency::task_group-Klasse oder der concurrency::parallel_invoke Algorithmus) Wenn Sie parallele Arbeit in kleinere Bestandteile zerlegen und auf diese kleinere Stücke dann warten müssen, um abzuschließen.

  • Verwenden Sie die - Methode, um concurrency::task::then Fortsetzungen zu erstellen.Eine Fortsetzung ist eine Aufgabe, wird asynchron nach einer anderen Aufgabe ausführt.Sie können eine beliebige Anzahl Fortsetzungen herstellen, um eine Kette der asynchronen Arbeit zu bilden.

  • Eine aufgabenbasierte Fortsetzung wird immer für die Ausführung beim vorherigen Aufgabe geplant, selbst wenn die Vorgängeraufgabe abgebrochen oder eine Ausnahme ausgelöst wird.

  • Verwenden Sie concurrency::when_all, eine Aufgabe zu erstellen, die abgeschlossen hat, nach jeder Member einer Gruppe von Aufgaben ".Verwenden Sie concurrency::when_any, eine Aufgabe zu erstellen, die, nachdem ein Member von einem Satz von Aufgaben abgeschlossen ist, abgeschlossen ist.

  • Aufgaben und Aufgabengruppen können am PPL-Abbruchsmechanismus teilnehmen.Weitere Informationen finden Sie unter Abbruch in der PPL.

  • Weitere Informationen wie die Runtime Ausnahmen behandelt die von Aufgaben und von Aufgabengruppen ausgelöst werden, finden Sie unter Ausnahmebehandlung in der Concurrency Runtime.

In diesem Dokument

  • Verwenden von Lambda-Ausdrücken

  • Die Aufgabe Klasse

  • Fortsetzungs-Aufgaben

  • Wertbasiert für aufgabenbasierte Fortsetzungen

  • Verfassen von Aufgaben

    • Die Funktion when_all

    • Die Funktion when_any

  • Verzögerte Aufgaben-Ausführung

  • Aufgabengruppen

  • task_group und structured_task_group im Vergleich

  • Beispiel

  • Stabile Programmierung

Verwenden von Lambda-Ausdrücken

Lambda-Ausdrücke sind eine häufige Methode, die Arbeit definieren, die von den Aufgaben und von Aufgabengruppen wegen ihrer knappen Syntax ausgeführt wird.Im Folgenden finden Sie einige Tipps zur Verwendung sie:

  • Da Aufgaben in der Regel auf Hintergrundthreads ausgeführt werden, die Objektlebensdauer, als Sie Variablen in Lambda-Ausdrücken aufzeichnen.Wenn Sie eine - Variable als Wert aufzeichnen, wird eine Kopie dieser Variablen im Lambda-Text erstellt.Wenn Sie durch Verweis aufzeichnen, wird eine Kopie nicht erstellt.Daher stellen Sie sicher, dass die Lebensdauer jeder Variable, die Sie als Verweis aufzeichnen, die Aufgabe die Verwendung es Noch vorhanden.

  • Im Allgemeinen zeichnen Sie Variablen nicht durch die zugeordnet werden auf dem Stapel auf.Dies bedeutet auch, dass Sie Membervariablen von Objekten nicht aufzeichnen möchten, die auf dem Stapel zugeordnet werden.

  • Stellen Sie über Variablen explizit, die Sie in Lambda-Ausdrücken aufzeichnen, die Ihnen helfen, zu bestimmen, welche Elemente Sie als Wert für als Verweis aufzeichnen.Aus diesem Grund wird jedoch nicht empfohlen, dass Sie die [=] oder [&] Optionen für Lambda-Ausdrücke verwenden.

Ein allgemeines Muster ist, wenn eine Aufgabe in einer Fortsetzungskette zu einer Variablen zugewiesen, und eine andere Aufgabe liest diese Variable.Sie können nicht als Wert aufzeichnen, da jede Fortsetzungsaufgabe eine weitere Kopie der Variablen ab würde.Für Stapel-zugeordnete Variablen können Sie nicht durch Verweis ebenfalls erfassen, da die - Variable möglicherweise nicht mehr gültig ist.

So beheben Sie dieses Problem, einen intelligenten Zeiger, wie std::shared_ptr verwenden, um die Variable zu umschließen und den intelligenten Zeiger als Wert übergeben wird.Indem vorgeführt wird, kann das zugrunde liegende Objekt zugewiesen werden und Lesen und wird die Aufgaben Beibehaltene Objekte, die es verwenden.Verwenden Sie diese Methode, wenn die Variable ein Zeiger oder mit Verweiszählung verwendet ein Handle (^) auf einem Windows Runtime-Objekt ist.Es folgt ein einfaches Beispiel:

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

using namespace concurrency;
using namespace std;

task<wstring> write_to_string()
{
    // Create a shared pointer to a string that is 
    // assigned to and read by multiple tasks.
    // By using a shared pointer, the string outlives
    // the tasks, which can run in the background after
    // this function exits.
    auto s = make_shared<wstring>(L"Value 1");

    return create_task([s] 
    {
        // Print the current value.
        wcout << L"Current value: " << *s << endl;
        // Assign to a new value.
        *s = L"Value 2";

    }).then([s] 
    {
        // Print the current value.
        wcout << L"Current value: " << *s << endl;
        // Assign to a new value and return the string.
        *s = L"Value 3";
        return *s;
    });
}

int wmain()
{
    // Create a chain of tasks that work with a string.
    auto t = write_to_string();

    // Wait for the tasks to finish and print the result.
    wcout << L"Final value: " << t.get() << endl;
}

/* Output:
    Current value: Value 1
    Current value: Value 2
    Final value: Value 3
*/

Weitere Informationen zu Lambda-Ausdrücken, finden Sie unter Lambda-Ausdrücke in C++.

[Anfang]

Die Aufgabe Klasse

Sie können die concurrency::task-Klasse verwenden, um Aufgaben in abhängige Vorgänge zu erstellen.Dieses Kompositionsmodell wird durch den Begriff von Fortsetzungen unterstützt.Eine Fortsetzung aktiviert den auszuführenden Code, wobei das vorherige oder das vorangehende, Aufgabe ausführt.Das Ergebnis der vorherigen Aufgabe wird als Eingabe an einem oder mehreren Fortsetzungsaufgaben übergeben.Wenn eine Vorgängeraufgabe abgeschlossen wurde, werden alle Fortsetzungsaufgaben, die darauf warten, für die Ausführung geplant.Jede Fortsetzungsaufgabe empfängt eine Kopie des Ergebnisses der Vorgängeraufgabe.Dagegen können diese Fortsetzungsaufgaben auch die vorangehenden Aufgaben für andere Fortsetzungen, sobald erstellen sie eine Kette von Aufgaben.Fortsetzungen helfen Ihnen, BeliebigLänge Ketten von Aufgaben zu erstellen, die bestimmte Abhängigkeiten zwischen ihnen haben.Außerdem kann eine Aufgabe am Abbruch entweder vor Aufgabenanfänge oder einer kooperative Weise teilnehmen, während sie ausgeführt wird.Weitere Informationen über dieses Abbruchmodell, finden Sie unter Abbruch in der PPL.

task ist eine Vorlagenklasse.Der Typparameter T ist der Typ des Ergebnisses, das von der Aufgabe erzeugt wird.Dieser Typ kann void sein, wenn die Aufgabe keinen Wert zurückgibt.T kann den const-Modifizierer nicht verwenden.

Wenn Sie eine Aufgabe erstellen, müssen Sie eine Arbeitsfunktion bereit,, die den Aufgabentext ausführt.Diese Arbeitsfunktion wird in Form einer Lambda-Funktion, Funktionsobjekte oder Funktionszeiger.Um auf eine Aufgabe warten ohne zu erhalten das Ergebnis auf zu beenden, rufen Sie die - Methode concurrency::task::wait.Die task::waitconcurrency::task_status-Methode gibt einen Wert zurück, der beschreibt, ob die Aufgabe abgeschlossen oder abgebrochen wurde.Um das Ergebnis der Aufgabe abzurufen, rufen Sie die concurrency::task::get-Methode auf.Dieses blockiert Methodenaufrufe task::wait, die auf der Aufgabe zu warten und daher Ausführung des aktuellen Threads, bis das Ergebnis verfügbar ist.

Im folgenden Beispiel wird gezeigt, wie eine Aufgabe wartet, sein Ergebnis erstellt und seinen Wert anzeigt.Die Beispiele in dieser Dokumentation verwenden Lambda-Funktionen, da sie eine kompaktere Syntax angeben.Sie können jedoch Funktionszeiger und Funktionsobjekte auch verwenden, wenn Sie Aufgaben verwenden.

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

using namespace concurrency;
using namespace std;

int wmain()
{
    // Create a task.
    task<int> t([]()
    {
        return 42;
    });

    // In this example, you don't necessarily need to call wait() because
    // the call to get() also waits for the result.
    t.wait();

    // Print the result.
    wcout << t.get() << endl;
}

/* Output:
    42
*/

Die concurrency::create_task-Funktion ermöglicht Ihnen, das auto-Schlüsselwort zu verwenden, anstatt, den Typ zu deklarieren.Betrachten Sie beispielsweise den folgenden Code, der die Identitätsmatrix erstellt und an:

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

using namespace concurrency;
using namespace std;

int wmain()
{
    task<array<array<int, 10>, 10>> create_identity_matrix([]
    {
        array<array<int, 10>, 10> matrix;
        int row = 0;
        for_each(begin(matrix), end(matrix), [&row](array<int, 10>& matrixRow) 
        {
            fill(begin(matrixRow), end(matrixRow), 0);
            matrixRow[row] = 1;
            row++;
        });
        return matrix;
    });

    auto print_matrix = create_identity_matrix.then([](array<array<int, 10>, 10> matrix)
    {
        for_each(begin(matrix), end(matrix), [](array<int, 10>& matrixRow) 
        {
            wstring comma;
            for_each(begin(matrixRow), end(matrixRow), [&comma](int n) 
            {
                wcout << comma << n;
                comma = L", ";
            });
            wcout << endl;
        });
    });

    print_matrix.wait();
}
/* Output:
    1, 0, 0, 0, 0, 0, 0, 0, 0, 0
    0, 1, 0, 0, 0, 0, 0, 0, 0, 0
    0, 0, 1, 0, 0, 0, 0, 0, 0, 0
    0, 0, 0, 1, 0, 0, 0, 0, 0, 0
    0, 0, 0, 0, 1, 0, 0, 0, 0, 0
    0, 0, 0, 0, 0, 1, 0, 0, 0, 0
    0, 0, 0, 0, 0, 0, 1, 0, 0, 0
    0, 0, 0, 0, 0, 0, 0, 1, 0, 0
    0, 0, 0, 0, 0, 0, 0, 0, 1, 0
    0, 0, 0, 0, 0, 0, 0, 0, 0, 1
*/

Sie können die create_task-Funktion verwenden, um den entsprechenden Vorgang zu erstellen.

auto create_identity_matrix = create_task([]
{
    array<array<int, 10>, 10> matrix;
    int row = 0;
    for_each(begin(matrix), end(matrix), [&row](array<int, 10>& matrixRow) 
    {
        fill(begin(matrixRow), end(matrixRow), 0);
        matrixRow[row] = 1;
        row++;
    });
    return matrix;
});

Wenn eine Ausnahme während der Ausführung einer Aufgabe ausgelöst wird, die Laufzeitmarschälle die Ausnahme im nachfolgenden Aufruf task::get oder zu task::wait oder einer aufgabenbasierte Fortsetzung.Weitere Informationen zu den Aufgabenausnahmebehandlungsmechanismus, finden Sie unter Ausnahmebehandlung in der Concurrency Runtime.

Ein Beispiel für task, concurrency::task_completion_event, Abbruch, finden Exemplarische Vorgehensweise: Verbinden von Verwendungsaufgaben und XML-HTTP-Anforderung (IXHR2).(Die task_completion_event-Klasse wird weiter unten in diesem Dokument beschrieben.)

TippTipp

Um Details dazu die Aufgaben in Windows Store App gelten, finden Sie unter Asynchronous programming in C++ und Erstellen von asynchronen Vorgängen in C++ für Windows Store-Apps.

[Anfang]

Fortsetzungs-Aufgaben

Bei der asynchronen Programmierung werden nach Abschluss eines asynchronen Vorgangs häufig ein zweiter Vorgang aufgerufen und Daten an diesen weitergegeben.In wird dieses durchgeführt, indem Rückrufmethoden erstellt.In der Concurrency Runtime wird die gleiche Funktionalität durch Fortsetzungsaufgaben bereitgestellt.Eine Fortsetzungsaufgabe (auch kurz als Fortsetzung bezeichnet) ist eine asynchrone Aufgabe, die von einer anderen Aufgabe, die wiederum als Vorgänger bezeichnet wird, nach deren Abschluss aufgerufen wird.Durch Fortsetzungen verwenden, können Sie:

  • Übergeben von Daten vom Vorgänger an die Fortsetzung.

  • Geben Sie die genauen Bedingungen, unter denen die Fortsetzung aufgerufen oder nicht aufgerufen.

  • Brechen Sie eine Fortsetzung, bevor sie oder gestartet, kooperativ ab, während sie ausgeführt wird.

  • Erstellen Sie Hinweise dazu, wie die Fortsetzung geplant werden soll.(Dies gilt für Windows Store nur App.Weitere Informationen finden Sie unter Erstellen von asynchronen Vorgängen in C++ für Windows Store-Apps.)

  • Rufen Sie mehrere Fortsetzungen vom gleichen Vorgänger auf.

  • Rufen Sie eine Fortsetzung wenn alle oder eines von mehreren einer der.

  • Verketten Sie Fortsetzungen nacheinander zu beliebiger Länge.

  • Verwenden Sie eine Fortsetzung, um Ausnahmen zu behandeln, die vom Vorgänger ausgelöst werden.

Diese Funktionen können Sie, um eine oder mehrere Aufgaben auszuführen, wenn die erste Aufgabe ausführt.Beispielsweise können Sie eine Fortsetzung erstellen, die eine Datei komprimiert, nachdem die erste Aufgabe sie vom Datenträger gelesen wird.

Im folgenden Beispiel wird das vorherige, um die concurrency::task::then-Methode zu verwenden, um eine Fortsetzung zu planen, die den Wert der vorhergehenden Aufgabe gibt, falls verfügbar ist.

// basic-continuation.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
    auto t = create_task([]() -> int
    {
        return 42;
    });

    t.then([](int result)
    {
        wcout << result << endl;
    }).wait();

    // Alternatively, you can chain the tasks directly and
    // eliminate the local variable.
    /*create_task([]() -> int
    {
        return 42;
    }).then([](int result)
    {
        wcout << result << endl;
    }).wait();*/
}

/* Output:
    42
*/

Sie können Aufgaben zu beliebiger Länge verketten und schachteln.Eine Aufgabe kann mehrere Fortsetzungen verfügen.Das folgende Beispiel veranschaulicht eine grundlegende Fortsetzungskette, die den Wert der vorherigen Aufgabe dreimal erhöht.

// continuation-chain.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
    auto t = create_task([]() -> int
    { 
        return 0;
    });

    // Create a lambda that increments its input value.
    auto increment = [](int n) { return n + 1; };

    // Run a chain of continuations and print the result.
    int result = t.then(increment).then(increment).then(increment).get();
    wcout << result << endl;
}

/* Output:
    3
*/

Eine Fortsetzung kann eine andere Aufgabe auch zurückgeben.Wenn kein Abbruch gibt, wird diese Aufgabe vor der folgenden Fortsetzung ausgeführt.Diese Technik wird, wie das asynchrone Auspacken.Das asynchrone Auspacken, ist hilfreich, wenn Sie zusätzliche im Hintergrund ausführen möchten, jedoch keine nicht die aktuelle Aufgabe, den aktuellen Thread zu blockieren.(Dies ist in Windows Store App üblich, in denen Fortsetzungen im UI-Thread ausgeführt werden können).Im folgenden Beispiel werden drei Aufgaben.Die erste Aufgabe gibt eine andere Aufgabe zurück, die vor einer Fortsetzungsaufgabe ausgeführt wird.

// async-unwrapping.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
    auto t = create_task([]()
    {
        wcout << L"Task A" << endl;

        // Create an inner task that runs before any continuation
        // of the outer task.
        return create_task([]()
        {
            wcout << L"Task B" << endl;
        });
    });

    // Run and wait for a continuation of the outer task.
    t.then([]()
    {
        wcout << L"Task C" << endl;
    }).wait();
}

/* Output:
    Task A
    Task B
    Task C
*/
Wichtiger HinweisWichtig

Wenn eine Fortsetzung einer Aufgabe eine geschachtelte Aufgabe des Typs N zurückgibt, verfügt die resultierende Aufgabe den Typ N, nicht task<N> und werden abgeschlossen, wenn die geschachtelte Aufgabe ausführt.Das bedeutet, dass die Fortsetzung das Auspacken der geschachtelten Aufgabe aus.

[Anfang]

Wertbasiert für aufgabenbasierte Fortsetzungen

Ein task-Objekt angegeben, dessen Rückgabetyp T ist, können Sie einen Wert des Typs T oder task<T> den Fortsetzungsaufgaben bereitstellen.Eine Fortsetzung, die Typ T sind, wird als wertbasierte Fortsetzung.Eine wertbasierte Fortsetzung wird für die Ausführung geplant, wenn die Vorgängeraufgabe ohne Fehler abgeschlossen ist und nicht abgebrochen.Eine Fortsetzung, die task<T>-Typ akzeptiert, während dessen - Parameter als aufgabenbasierte Fortsetzung bekannt sind.Eine aufgabenbasierte Fortsetzung wird immer für die Ausführung beim vorherigen Aufgabe geplant, selbst wenn die Vorgängeraufgabe abgebrochen oder eine Ausnahme ausgelöst wird.Sie können task::get dann aufrufen, um das Ergebnis der vorherigen Aufgabe abzurufen.Wenn die Vorgängeraufgabe abgebrochen wurde, löst task::getconcurrency::task_canceled aus.Wenn die Vorgängeraufgabe eine Ausnahme ausgelöst wurde, task::get löst diese Ausnahme erneut aus.Eine aufgabenbasierte Fortsetzung wird nicht markiert, wie abgebrochen, wenn die Vorgängeraufgabe abgebrochen wird.

[Anfang]

Verfassen von Aufgaben

In diesem Abschnitt werden die concurrency::when_all und concurrency::when_any-Funktionen, die Ihnen helfen können, mehrere Aufgaben zusammensetzt, allgemeine Muster zu implementieren.

Dd492427.collapse_all(de-de,VS.110).gifDie Funktion when_all

Die when_all-Funktion erzeugt eine Aufgabe, die nach einem Satz vollständige Aufgaben ".Diese Funktion gibt std::vector ein - Objekt zurück, das das Ergebnis jeder Aufgabe im Satz enthalten ist.Im Folgenden einfachen Beispiel wird when_all, eine Aufgabe zu erstellen, die den Abschluss von drei anderen Aufgaben darstellt.

// join-tasks.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <array>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
    // Start multiple tasks.
    array<task<void>, 3> tasks = 
    {
        create_task([] { wcout << L"Hello from taskA." << endl; }),
        create_task([] { wcout << L"Hello from taskB." << endl; }),
        create_task([] { wcout << L"Hello from taskC." << endl; })
    };

    auto joinTask = when_all(begin(tasks), end(tasks));

    // Print a message from the joining thread.
    wcout << L"Hello from the joining thread." << endl;

    // Wait for the tasks to finish.
    joinTask.wait();
}

/* Sample output:
    Hello from the joining thread.
    Hello from taskA.
    Hello from taskC.
    Hello from taskB.
*/
HinweisHinweis

Die Aufgaben, die Sie zu when_all übergeben, müssen einheitlich sein.Das heißt, sie müssen alle zurück denselben Typ.

Sie können die && Syntax verwenden, um eine Aufgabe zu erstellen, die die, nachdem ein Satz von Aufgaben ausführen, wie im folgenden Beispiel gezeigt abgeschlossen hat.

auto t = t1 && t2; // same as when_all

Es ist üblich, eine Fortsetzung zusammen mit when_all zu verwenden, um Aktionen ausführen nach einem Satz Aufgabe.Im folgenden Beispiel wird das vorherige, um die Summe von drei Aufgaben zu drucken, dass jedes ein int Ergebnis liefert.

// Start multiple tasks.
array<task<int>, 3> tasks =
{
    create_task([]() -> int { return 88; }),
    create_task([]() -> int { return 42; }),
    create_task([]() -> int { return 99; })
};

auto joinTask = when_all(begin(tasks), end(tasks)).then([](vector<int> results)
{
    wcout << L"The sum is " 
          << accumulate(begin(results), end(results), 0)
          << L'.' << endl;
});

// Print a message from the joining thread.
wcout << L"Hello from the joining thread." << endl;

// Wait for the tasks to finish.
joinTask.wait();

/* Output:
    Hello from the joining thread.
    The sum is 229.
*/

In diesem Beispiel können Sie task<vector<int>> auch angeben, um eine aufgabenbasierte Fortsetzung zu erzeugen.

WarnhinweisVorsicht

Weisen Sie ggf. in einem Satz von Aufgaben abgebrochen wird oder eine Ausnahme auslöst, schließt eine Arbeit zu when_all sofort und wartet nicht auf die übrigen Aufgaben ab zu beenden.Wenn eine Ausnahme ausgelöst wird, löst die Laufzeit die Ausnahme erneut task::get oder wenn Sie task::wait auf dem task-Objekt aufrufen, das when_all zurückgibt.Wenn die Methode mehr als einen mit Aufgaben, die Laufzeit eines von ihnen auswählt.Wenn von eine Ausnahme auslöst, stellen Sie sicher, dass Sie auf alle Aufgaben warten abzuschließen.

[Anfang]

Dd492427.collapse_all(de-de,VS.110).gifDie Funktion when_any

Die when_any-Funktion erzeugt eine Aufgabe, die abgeschlossen wird, wenn die erste Aufgabe in einem Satz von Aufgaben ".Diese Funktion gibt std::pair ein - Objekt zurück, das das Ergebnis der abgeschlossenen Aufgabe und Indizes dieser Aufgabe im Satz enthalten ist.

Die when_any-Funktion ist insbesondere in den folgenden Szenarien nützlich:

  • Redundante Vorgänge.Betrachten Sie einen Algorithmus oder einen Vorgang, die auf verschiedene Weise ausgeführt werden können.Sie können die when_any-Funktion verwenden, um den Vorgang auszuwählen, dem Ende zunächst und dann die verbleibenden Vorgänge abbrechen.

  • Verschachtelte Vorgänge.Sie können mehrere Vorgänge beginnen, die when_any-Funktion beenden und verwenden müssen, um Ergebnisse als Ende jedes Vorgangs zu verarbeiten.Nachdem die Ende mit einen Operationen, Sie eine oder mehrere zusätzliche Aufgaben starten können.

  • Eingeschränkte Vorgänge.Sie können die when_any-Funktion verwenden, um das vorherige Szenario zu erweitern, indem Sie die Anzahl von gleichzeitigen Operationen einschränken.

  • Abgelaufene Vorgänge.Sie können die when_any-Funktion verwenden, um zwischen einer oder mehreren Aufgaben und einer Aufgabe auszuwählen, die nach einem bestimmten Zeitpunkt beendet.

Wie bei when_all, ist es häufig, eine Fortsetzung, die when_any, auf von auszuführende Aktion das erste in einem Satz von bis.Im Folgenden einfachen Beispiel wird when_any, eine Aufgabe zu erstellen, die abgeschlossen wird, wenn das erste von drei anderen Aufgaben ".

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

using namespace concurrency;
using namespace std;

int wmain()
{
    // Start multiple tasks.
    array<task<int>, 3> tasks = {
        create_task([]() -> int { return 88; }),
        create_task([]() -> int { return 42; }),
        create_task([]() -> int { return 99; })
    };

    // Select the first to finish.
    when_any(begin(tasks), end(tasks)).then([](pair<int, size_t> result)
    {
        wcout << "First task to finish returns "
              << result.first
              << L" and has index "
              << result.second
              << L'.' << endl;
    }).wait();
}

/* Sample output:
    First task to finish returns 42 and has index 1.
*/

In diesem Beispiel können Sie task<pair<int, size_t>> auch angeben, um eine aufgabenbasierte Fortsetzung zu erzeugen.

HinweisHinweis

Wie bei when_all, müssen die Aufgaben, die Sie zu when_any übergeben, alle zurück denselben Typ.

Sie können die || Syntax verwenden, um eine Aufgabe zu erzeugen, die nach der ersten Aufgabe in einem Satz von Aufgaben abgeschlossen hat, wie im folgenden Beispiel gezeigt abgeschlossen hat.

auto t = t1 || t2; // same as when_any

[Anfang]

Verzögerte Aufgaben-Ausführung

Manchmal ist es notwendig, die Ausführung einer Aufgabe zu verzögern, bis eine Bedingung erfüllt ist, oder eine Aufgabe als Reaktion auf ein externes Ereignis zu starten.In der asynchronen Programmierung, müssen Sie möglicherweise eine Aufgabe als Reaktion auf ein E/A-Komplettierungs-Ereignis starten.

Zwei Möglichkeiten, dies zu erreichen, sind eine Fortsetzung zu verwenden oder eine Aufgabe und einen Wartevorgang für ein Ereignis innerhalb der Arbeitsfunktion der Aufgabe zu starten.Es gibt jedoch Fälle, in denen es nicht möglich, eine dieser Techniken verwendet wird.Wenn beispielsweise eine Fortsetzung zu erstellen, müssen Sie die Vorgängeraufgabe haben.Wenn Sie jedoch nicht die Vorgängeraufgabe haben, können Sie ein Aufgabenabschlussereignis und eine neuere Kette erstellen, die Abschlussereignis zur Vorgängeraufgabe, sofern verfügbar ist.Da wartende Aufgabe auch einen Thread blockiert, können Sie Aufgabenabschlussereignisse verwenden, um Aufgaben auszuführen, wenn ein asynchroner Vorgang abgeschlossen wurde, und geben daher einen Thread frei.

Die concurrency::task_completion_event-Klassenhilfen vereinfachen solche Zusammensetzung von Aufgaben.Wie die task-Klasse ist der Typparameter T der Typ des Ergebnisses, das von der Aufgabe erzeugt wird.Dieser Typ kann void sein, wenn die Aufgabe keinen Wert zurückgibt.T kann den const-Modifizierer nicht verwenden.In der Regel wird ein task_completion_event-Objekt an einen Thread oder Aufgabe bereitgestellt, die Sie angeben, wenn der Wert dafür verfügbar ist.Gleichzeitig werden eine oder mehrere Aufgaben als Listener für dieses Ereignis festgelegt.Wenn das Ereignis festgelegt wird, schließen die Listeneraufgaben ab und ihre Fortsetzungen geplant werden, um ausgeführt zu werden.

Ein Beispiel für task_completion_event, eine Aufgabe zu implementieren, die abgeschlossen wird, nachdem eine Verzögerung, Gewusst wie: Erstellen einer Aufgabe, die nach einer Verzögerung abgeschlossen wird finden.

[Anfang]

Aufgabengruppen

Eine Reihe von Aufgaben wird in einer Aufgabengruppe organisiert.Aufgabengruppen verschieben Aufgaben in eine Arbeitsübernahme-Warteschlange.Der Planer entfernt Aufgaben aus dieser Warteschlange und führt sie auf verfügbaren Computerressourcen aus.Nachdem Sie einer Aufgabengruppe Aufgaben hinzugefügt haben, können Sie warten, bis alle Aufgaben aufgeführt wurden, oder Sie können Aufgaben abbrechen, die noch nicht gestartet wurden.

Die PPL verwendet die concurrency::task_group und concurrency::structured_task_group-Klassen, um Aufgabengruppen mit der und die concurrency::task_handle-Klasse, um die Aufgaben dargestellt, die in diese Gruppen ausgeführt werden.In der task_handle-Klasse wird der Code gekapselt, der die Arbeit ausführt.Wie die task-Klasse steht die Arbeitsfunktion in Form einer Lambda-Funktion, Funktionsobjekte oder Funktionszeiger.In der Regel ist es nicht erforderlich, direkt mit task_handle-Objekten zu arbeiten.Stattdessen übergeben Sie Arbeitsfunktionen an eine Aufgabengruppe, die die task_handle-Objekte erstellt und verwaltet.

Die PPL unterscheidet zwischen zwei Kategorien von Aufgabengruppen: unstrukturierte Aufgabengruppen und strukturierte Aufgabengruppen.Die PPL verwendet die task_group-Klasse, um unstrukturierte Aufgabengruppen und die structured_task_group-Klasse dargestellt, und strukturierte Aufgabengruppen mit der.

Wichtiger HinweisWichtig

Die PPL definiert auch den concurrency::parallel_invoke Algorithmus, der die structured_task_group-Klasse, um einen Satz von Aufgaben parallel auszuführen.Da der parallel_invoke-Algorithmus eine kompaktere Syntax aufweist, wird empfohlen, diesen, sofern möglich, anstelle der structured_task_group-Klasse zu verwenden.Der parallel_invoke-Algorithmus wird im Thema Parallele Algorithmen ausführlicher beschrieben.

Verwenden Sie parallel_invoke, um mehrere unabhängige Aufgaben gleichzeitig auszuführen und sofort darauf zu warten, dass alle Aufgaben abgeschlossen sind.Diese Technik wird häufig als Gabelungs- und Joinparallelität.Verwenden Sie task_group, um mehrere unabhängige Aufgaben gleichzeitig auszuführen und später darauf zu warten, dass allle Aufgaben abgeschlossen sind.Beispielsweise können Sie einem task_group-Objekt Aufgaben hinzufügen und in einer anderen Funktion oder einem anderen Thread darauf warten, dass die Aufgaben beendet werden.

Aufgabengruppen unterstützen das Konzept eines Abbruchs.Mit einem Abbruch können Sie für alle aktiven Aufgaben angeben, dass der gesamte Vorgang abgebrochen werden soll.Durch den Abbruch wird außerdem verhindert, dass Aufgaben gestartet werden, die noch nicht gestartet wurden.Weitere Informationen über das Abbrechen finden Sie unter Abbruch in der PPL.

Die Laufzeit stellt außerdem ein Modell für die Ausnahmebehandlung bereit, mit dem Sie eine Ausnahme für eine Aufgabe auslösen und behandeln können, während Sie darauf warten, das die zugeordnete Aufgabengruppe fertig gestellt wird.Weitere Informationen zu diesem Modell für die Behandlung von Ausnahmen finden Sie unter Ausnahmebehandlung in der Concurrency Runtime.

[Anfang]

task_group und structured_task_group im Vergleich

Obwohl empfohlen, dass Sie task_group oder parallel_invoke anstelle der structured_task_group-Klasse verwenden, es gibt jedoch Fälle, in denen Sie structured_task_group verwenden möchten dem beispielsweise einen parallelen Algorithmus schreiben, der eine variable Anzahl von Aufgaben ausführt oder Unterstützung für Abbrüche erfordert.In diesem Abschnitt werden die Unterschiede zwischen der task_group-Klasse und der structured_task_group-Klasse erläutert.

Die task_group-Klasse ist threadsicher.Daher können Sie Aufgaben einem task_group-Objekt von mehreren Threads hinzufügen und ein task_group-Objekt von mehreren Threads auf warten oder abbrechen.Das Erstellen und Zerstören eines structured_task_group-Objekts muss im gleichen lexikalischen Gültigkeitsbereich erfolgen.Darüber hinaus müssen alle Vorgänge für ein structured_task_group-Objekt im gleichen Thread ausgeführt werden.Die Ausnahme von dieser Regel ist concurrency::structured_task_group::cancel und die Concurrency::structured_task_group::is_canceling-Methoden.Eine untergeordnete Aufgabe kann diese Methoden aufrufen, um die übergeordnete Aufgabengruppe abzubrechen oder das Abbrechen jederzeit zu überprüfen.

Sie können zusätzliche Aufgaben für ein task_group-Objekt ausführen, nachdem Sie die concurrency::task_group::wait oder concurrency::task_group::run_and_wait-Methode aufrufen.Umgekehrt können Sie zusätzliche Aufgaben für ein structured_task_group-Objekt ausführen, nachdem Sie die concurrency::structured_task_group::wait oder concurrency::structured_task_group::run_and_wait-Methoden aufrufen.

Da die structured_task_group-Klasse nicht threadübergreifend synchronisiert, ist ihr Ausführungsaufwand im Vergleich zur task_group-Klasse geringer.Wenn die Planung von Arbeit für mehrere Threads nicht Teil eines Problems ist und der parallel_invoke-Algorithmus nicht verwendet werden kann, können Sie mit der structured_task_group-Klasse leistungsfähigeren Code schreiben.

Wenn Sie ein structured_task_group-Objekt in einem anderen structured_task_group-Objekt verwenden, muss das innere Objekt abgeschlossen und zerstört sein, bevor das äußere Objekt beendet wird.Bei der task_group-Klasse ist die Fertigstellung geschachtelter Aufgabengruppen vor der äußeren Gruppe nicht erforderlich.

Unstrukturierte Aufgabengruppen und strukturierte Aufgabengruppen verwenden Aufgabenhandles auf unterschiedliche Weise.Sie können Arbeitsfunktionen direkt an ein task_group-Objekt übergeben; das Aufgabenhandle wird unmittelbar vom task_group-Objekt für Sie erstellt und verwaltet.Die structured_task_group-Klasse erfordert die Verwaltung eines task_handle-Objekts für jede Aufgabe.Jedes task_handle-Objekt muss über die gesamte Lebensdauer des zugeordneten structured_task_group-Objekts hinweg gültig sein.Verwenden Sie die concurrency::make_task-Funktion, um ein task_handle-Objekt, wie im Folgenden grundlegenden Beispiel veranschaulicht:

// make-task-structure.cpp
// compile with: /EHsc
#include <ppl.h>

using namespace concurrency;

int wmain()
{
   // Use the make_task function to define several tasks.
   auto task1 = make_task([] { /*TODO: Define the task body.*/ });
   auto task2 = make_task([] { /*TODO: Define the task body.*/ });
   auto task3 = make_task([] { /*TODO: Define the task body.*/ });

   // Create a structured task group and run the tasks concurrently.

   structured_task_group tasks;

   tasks.run(task1);
   tasks.run(task2);
   tasks.run_and_wait(task3);
}

Mit einer Routine für die Stapelzuweisung wie _malloca oder einer Containerklasse wie std::vector können Sie Aufgabenhandles für eine variable Anzahl von Aufgaben verwalten.

task_group und structured_task_group unterstützen die Möglichkeit eines Abbruchs.Weitere Informationen über das Abbrechen finden Sie unter Abbruch in der PPL.

[Anfang]

Beispiel

Im folgenden grundlegenden Beispiel wird die Verwendung von Aufgabengruppen veranschaulicht.In diesem Beispiel werden vom parallel_invoke-Algorithmus zwei Aufgaben gleichzeitig ausgeführt.In jeder Aufgabe werden einem task_group-Objekt untergeordnete Aufgaben hinzugefügt.Die task_group-Klasse ermöglicht das zeitgleiche Hinzufügen für mehrere Aufgaben.

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

using namespace concurrency;
using namespace std;

// Prints a message to the console.
template<typename T>
void print_message(T t)
{
   wstringstream ss;
   ss << L"Message from task: " << t << endl;
   wcout << ss.str(); 
}

int wmain()
{  
   // A task_group object that can be used from multiple threads.
   task_group tasks;

   // Concurrently add several tasks to the task_group object.
   parallel_invoke(
      [&] {
         // Add a few tasks to the task_group object.
         tasks.run([] { print_message(L"Hello"); });
         tasks.run([] { print_message(42); });
      },
      [&] {
         // Add one additional task to the task_group object.
         tasks.run([] { print_message(3.14); });
      }
   );

   // Wait for all tasks to finish.
   tasks.wait();
}

Nachfolgend wird eine Beispielausgabe für dieses Beispiel angezeigt:

Message from task: Hello
Message from task: 3.14
Message from task: 42

Da die Aufgaben vom parallel_invoke-Algorithmus gleichzeitig ausgeführt werden, kann sich die Reihenfolge der Ausgabemeldungen unterscheiden.

Umfassende Beispiele zur Verwendung des parallel_invoke-Algorithmus finden Sie unter Gewusst wie: Verwenden von parallel_invoke zum Schreiben einer Runtime für paralleles Sortieren und Gewusst wie: Ausführen von parallelen Vorgängen mithilfe von parallel_invoke.Ein vollständiges Beispiel zur Implementierung asynchroner Futures mit der task_group-Klasse finden Sie unter Exemplarische Vorgehensweise: Implementieren von Futures.

[Anfang]

Stabile Programmierung

Stellen Sie sicher, dass Sie die Rolle des Abbruchs und Ausnahmebehandlung verstehen, wenn Sie Aufgaben, Aufgabengruppen und parallele Algorithmen verwenden.Beispielweise kann eine abgebrochene Aufgabe in einer Struktur paralleler Arbeitsaufgaben dazu führen, dass untergeordnete Aufgaben nicht ausgeführt werden.Dies kann Probleme verursachen, wenn eine der untergeordneten Aufgaben einen Vorgang ausführen soll, der für die Anwendung von Bedeutung ist, beispielsweise das Freigeben einer Ressource.Wenn eine untergeordnete Aufgabe eine Ausnahme auslöst, kann diese Ausnahme außerdem über einen Objektdestruktor weitergeben werden und nicht definiertes Verhalten in der Anwendung auslösen.Ein Beispiel, in dem diese Punkte veranschaulicht werden, finden Sie im Abschnitt Understand how Cancellation and Exception Handling Affect Object Destruction der empfohlenen Vorgehensweisen im Dokument zur Parallel Patterns Library.Weitere Informationen zur Ausnahmebehandlung sowie zu Abbruchmodellen in der PPL finden Sie unter Abbruch in der PPL und Ausnahmebehandlung in der Concurrency Runtime.

[Anfang]

Verwandte Themen

Titel

Description

Gewusst wie: Verwenden von parallel_invoke zum Schreiben einer Runtime für paralleles Sortieren

Erläutert, wie die Leistung des bitonischen Sortieralgorithmus mit dem parallel_invoke-Algorithmus verbessert werden.

Gewusst wie: Ausführen von parallelen Vorgängen mithilfe von parallel_invoke

Erläutert, wie die Leistung eines Programms mit dem parallel_invoke-Algorithmus verbessert werden kann, das mehrere Vorgänge in einer freigegebenen Datenquelle ausführt.

Gewusst wie: Erstellen einer Aufgabe, die nach einer Verzögerung abgeschlossen wird

Zeigt, wie task, cancellation_token_source, cancellation_token und task_completion_event-Klassen verwendet, um eine Aufgabe zu erstellen, die nach einer Verzögerung abgeschlossen hat.

Exemplarische Vorgehensweise: Implementieren von Futures

Zeigt, wie die vorhandene Funktionalität in der Concurrency Runtime kombiniert werden kann, um mehr Funktionalität zu erreichen.

Parallel Patterns Library (PPL)

Beschreibt die PPL, die ein obligatorisches Programmiermodell zum Entwickeln gleichzeitiger Anwendungen bereitstellt.

Verweise

task-Klasse (Concurrency Runtime)

task_completion_event-Klasse

when_all-Funktion

when_any-Funktion

task_group-Klasse

parallel_invoke-Funktion

structured_task_group-Klasse