Udostępnij za pośrednictwem


Zadanie równoległości (współbieżności Runtime)

Dokument ten opisuje rolę zadań i grup zadań w czasie wykonywania współbieżności.A zadania jest jednostką pracy, który wykonuje określone zadanie.Zadania zazwyczaj uruchamia równolegle z innymi zadaniami i może być rozłożony na zadania dodatkowe, bardziej precyzyjne.A grupy zadań organizuje zbiorem zadań.

Używanie zadań po napisać kod asynchroniczny niektórych operacji występuje po zakończeniu operacji asynchronicznych.Na przykład może użyć zadania asynchronicznie odczytać z pliku i zadanie utrzymania jest wyjaśnione w dalszej części tego dokumentu do przetwarzania danych, gdy stanie się dostępny.Odwrotnie za pomocą grup zadań rozkładać równoległych pracę na mniejsze kawałki.Załóżmy na przykład, algorytm cyklicznych, która dzieli Praca pozostała na dwie partycje.Grupy zadań umożliwia równoczesne te partycje i zaczekaj, aż podzielonych pracy do wykonania.

PoradaPorada

Zastosowanie tej samej procedury dla każdego elementu kolekcji równolegle, należy używać algorytmu równoległego, takich jak concurrency::parallel_for, zadania lub grupy zadań.Aby uzyskać więcej informacji na temat algorytmów równoległych zobacz Algorytmy równoległe.

Punkty klucza

  • Jeśli zmienne do wyrażenia lambda przez odwołanie, musi gwarantować istnienia zmiennej obowiązuje, dopóki nie zakończy się zadanie.

  • Użyj zadania ( concurrency::task klasy) kiedy napisać kod asynchroniczny.

  • Użyj grupy zadań (takich jak concurrency::task_group klasy lub concurrency::parallel_invoke algorytm) kiedy należy rozkładać równoległych pracę na mniejsze kawałki i zaczekaj na tych mniejsze kawałki zakończyć.

  • Użyj concurrency::task::then metodę w celu utworzenia kontynuacji.A kontynuacji jest zadanie uruchamiane asynchronicznie, po zakończeniu innego zadania.Można połączyć z dowolną liczbę kontynuacji do utworzenia łańcucha asynchroniczne.

  • Po zakończeniu zadania antecedent, nawet gdy antecedent zadanie zostało anulowane lub zgłasza wyjątek, kontynuacji opartych na zadaniach zawsze jest zaplanowane do wykonania.

  • Użyj concurrency::when_all utworzyć zadanie kończy się po zakończeniu każdego członka zestawu zadań.Użyj concurrency::when_any utworzyć zadanie kończy się po zakończeniu jeden członek zestawu zadań.

  • Zadań i zadań grup mogą uczestniczyć w PPL mechanizmu anulowania.Aby uzyskać więcej informacji, zobacz Anulowanie w PPL.

  • Aby dowiedzieć się, jak środowisko wykonawcze obsługuje wyjątki, które są generowane przez zadań i grup zadań, zobacz Obsługa wyjątków w Runtime współbieżności.

W tym dokumencie

  • Za pomocą wyrażenia Lambda

  • Zadania, klasa

  • Kontynuacja zadań

  • Wartość oparta kontra kontynuacji opartych na zadaniach

  • Tworzenie zadań

    • Funkcja when_all

    • Funkcja when_any

  • Wykonanie zadania opóźnione

  • Grupy zadań

  • Porównanie task_group do structured_task_group

  • Przykład

  • Stabilne Programowanie

Za pomocą wyrażenia Lambda

Wyrażenia lambda są powszechnym sposobem definiowania pracy wykonanej przez zadań i grup zadań z powodu ich zwięzłe składni.Oto kilka porad dotyczących ich używania:

  • Ponieważ zadania zwykle uruchamiane na tle wątków, być świadomi istnienia obiektu podczas przechwytywania zmienne w wyrażeniach lambda.Podczas przechwytywania zmiennej wartości, wykonywana jest kopia tej zmiennej w treści lambda.Podczas przechwytywania danych przez odwołanie nie jest wykonywana jest kopia.Dlatego upewnij się, że, okres istnienia jakichkolwiek zmiennej, która przechwytywania przez odniesienie outlives zadania, która go używa.

  • Ogólnie rzecz biorąc, nie należy przechwytywać zmiennych, przez który są przydzielane na stosie.To oznacza, że nie należy przechwytywać zmienne obiektów, które są przydzielane na stosie.

  • Być wyraźnie o zmiennych przechwytywania w wyrażeniach lambda ułatwiające identyfikowanie, co użytkownik jest przechwytywanie przez wartość kontra przez odniesienie.Z tego powodu nie jest zalecane używanie [=] lub [&] opcje dla wyrażenia lambda.

Jednego wspólnego wzorca jest podczas jednego zadania w łańcuchu kontynuacji przypisuje zmiennej i innego zadania odczytuje tej zmiennej.Wartość nie może przechwytywać, ponieważ każde zadanie utrzymania będzie posiadał kopię tej zmiennej.Nie przydzielone stosu zmiennych, również można przechwycić przez odniesienie, ponieważ zmienna przestaną być ważne.

Aby rozwiązać ten problem, użyj inteligentne wskaźnik, takich jak std::shared_ptr, aby otoczyć zmiennej i przekazać wskaźnik inteligentnych przez wartość.W ten sposób obiektu źródłowego można przypisać i odczytać i będzie przetrwać dłużej niż zadania, które go używają.Użyj tej techniki, nawet wtedy, gdy zmienna jest wskaźnik lub uchwyt zliczane odniesienia (^) do obiektu Windows Runtime.Oto prosty przykład:

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

Więcej informacji o wyrażeniach lambda, zobacz Lambda wyrażenia w języku C++.

Top

Zadania, klasa

Można użyć concurrency::task klasy do redagowania zadania do zestawu zależnego operacji.Ten model składu jest obsługiwany przez pojęcie kontynuacji.Gdy wykonanie kodu umożliwia kontynuację poprzedniego, lub antecedent, kończy zadanie.Wynik antecedent zadania jest przekazywana jako dane wejściowe do jednego lub więcej zadań kontynuacji.Po zakończeniu antecedent zadanie utrzymania zadań, które oczekują na nim są zaplanowane do wykonania.Każde zadanie utrzymania otrzymuje kopię wyników zadań antecedent.Z kolei tych zadań utrzymania może być antecedent zadania dla innych kontynuacji, tworząc łańcuch zadań.Kontynuacji ułatwiają tworzenie łańcuchów dowolnej długości zadania, które mają szczególne zależności między nimi.Ponadto zadania mogą uczestniczyć na anulowanie rozpoczyna się przed zadań lub w sposób współpracy, jest uruchomiona.Więcej informacji o modelu anulowania, zobacz Anulowanie w PPL.

taskjest klasa szablonu.Parametr typu T jest typem wynik, który jest wytwarzany przez zadanie.Tego typu może być void , jeśli zadanie nie zwraca wartości.Tnie można użyć const modyfikator.

Podczas tworzenia zadania zapewnienia pracy funkcji , która wykonuje zadania organu.Ta funkcja pracę modułowym funkcji lambda, wskaźnik funkcji lub funkcji obiekt.Oczekiwania zadania zakończyć bez uzyskania wyniku wywołania concurrency::task::wait metody.task::wait Metoda zwraca concurrency::task_status wartość, która opisuje, czy zadanie zostało ukończone lub anulowane.Aby uzyskać wynik zadania, wywołać concurrency::task::get metody.Ta metoda wymaga task::wait do poczekać na zakończenie, a zatem wykonanie bloki bieżącego wątku zadania wynik jest dostępna.

Poniższy przykład pokazuje, jak utworzyć zadanie, czekać na jej wynik i wyświetl jego wartość.Przykłady w tej dokumentacji funkcji lambda, ponieważ zapewniają one bardziej zwięzłe składni.Jednakże umożliwia także wskaźników funkcji i obiektów funkcji używania zadań.

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

Concurrency::create_task funkcja umożliwia używanie auto słowa kluczowego zamiast deklarowania typu.Na przykład rozważmy następujący kod tworzy i drukuje macierz:

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

Można użyć create_task funkcji do tworzenia równoważne operacji.

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

Jeśli wyjątek podczas wykonywania zadania, runtime marshals ten wyjątek w wywołaniu kolejnych task::get lub task::wait, lub kontynuacji opartych na zadaniach.Aby uzyskać więcej informacji na temat zadania mechanizm obsługi wyjątków, zobacz Obsługa wyjątków w Runtime współbieżności.

Na przykład, który używa task, concurrency::task_completion_event, anulowanie, zobacz Przewodnik: Łączenie, za pomocą zadań i żądania HTTP XML (IXHR2).( task_completion_event Klasy opisano w dalszej części tego dokumentu.)

PoradaPorada

Aby poznać szczegóły, które są specyficzne dla zadań w Windows Store aplikacji, zobacz Asynchronous programming in C++ i Tworzenie operacji asynchronicznych w języku C++ dla aplikacji sklepu Windows Store.

Top

Kontynuacja zadań

Asynchroniczne programowania jest często stosowane w jednej operacji asynchronicznych po zakończeniu operacji drugiego i przekazywanie danych do niego.Tradycyjnie to przy użyciu metody wywołania zwrotnego.W Runtime współbieżności samą funkcjonalność jest zapewniona przez zadań utrzymania.Zadanie utrzymania (znany również jako kontynuacji) jest asynchroniczne zadania, który jest wywoływany przez innego zadania, co jest znane jako antecedent, po zakończeniu antecedent.Za pomocą kontynuacji, można:

  • Przekazywanie danych od antecedent do kontynuacji.

  • Należy określić dokładne warunki w jakich kontynuacji powoływać lub nie jest wywoływany.

  • Anuluj kontynuacji, przed jego uruchomieniem albo wspólnie jest uruchomiona.

  • Dostarcza wskazówek dotyczących sposobu zaplanowane kontynuacji.(Dotyczy to Windows Store tylko aplikacje.Aby uzyskać więcej informacji, zobacz Tworzenie operacji asynchronicznych w języku C++ dla aplikacji sklepu Windows Store.)

  • Wywołać kontynuacji wiele z tych samych antecedent.

  • Wywołać jeden kontynuacji po ukończeniu wszystkie lub niektóre przypadki wielu.

  • Łańcuch kontynuacji po kolei do dowolnej długości.

  • Użyj kontynuację obsługi wyjątków, które są generowane przez antecedent.

Funkcje te umożliwiają wykonanie jednego lub więcej zadań po zakończeniu pierwszego zadania.Na przykład można utworzyć kontynuacji, który kompresuje plik po pierwszym zadaniem odczytuje go z dysku.

Poniższy przykład modyfikuje poprzedniego aby użyć concurrency::task::then metody zaplanować kontynuacji, która drukuje wartość antecedent zadań, gdy jest dostępne.

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

Można łańcucha i zagnieździć zadań do dowolnej długości.Zadanie może mieć również wiele kontynuacji.Poniższy przykład ilustruje łańcucha kontynuacji podstawowego, który zwiększa wartość poprzedniego zadania trzy razy.

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

Kontynuacja może również zwracać innego zadania.Jeśli nie anulowania to zadanie jest wykonywane przed kolejnych kontynuacji.Ta technika jest znany jako asynchronicznego rozpakowanie.Rozpakowanie asynchroniczne jest przydatne, gdy do wykonania dodatkowej pracy w tle, ale nie chcesz blokować bieżącego wątku bieżącego zadania.(Jest to często w Windows Store aplikacji, gdzie kontynuacji można uruchomić wątku interfejsu użytkownika).W poniższym przykładzie przedstawiono trzy zadania.Pierwsze zadanie zwraca inne zadanie jest uruchamiane przed zadaniem kontynuacji.

// 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
*/
Ważna uwagaWażne

Kiedy kontynuacji zadania zwraca zagnieżdżonych zadanie typu N, wynikowy zadanie ma typ N, nie task<N>i kończy po zakończeniu zadania zagnieżdżonych.Innymi słowy kontynuacji wykonuje rozpakowanie zagnieżdżonych zadania.

Top

Wartość oparta kontra kontynuacji opartych na zadaniach

Biorąc pod uwagę task obiekt, którego typ zwracany jest T, można podać wartość typu T lub task<T> do jej zadań kontynuacji.Kontynuacja, która ma typ T jest znany jako kontynuacji opartych na wartość.Kontynuacja wartość oparta jest zaplanowane do wykonania po antecedent zadanie kończy się bez błędów i nie zostanie anulowane.Kontynuacji, która ma typ task<T> jako jej parametr jest znany jako z zadaniami kontynuacji.Po zakończeniu zadania antecedent, nawet gdy antecedent zadanie zostało anulowane lub zgłasza wyjątek, kontynuacji opartych na zadaniach zawsze jest zaplanowane do wykonania.Można wywołać task::get Aby uzyskać wynik zadania antecedent.Jeśli antecedent zadanie zostało anulowane, task::get wyrzuca concurrency::task_canceled.Jeśli zadanie antecedent zwrócił wyjątek, task::get rethrows ten wyjątek.Kontynuacja opartych na zadaniach nie jest oznaczony jako anulowany po anulowaniu swojego zadania antecedent.

Top

Tworzenie zadań

W tej sekcji opisano concurrency::when_all i concurrency::when_any funkcje, które mogą pomóc redagowania wielu zadań do wykonania typowe wzorce.

Dd492427.collapse_all(pl-pl,VS.110).gifFunkcja when_all

when_all Funkcja tworzy zadanie kończy się po zakończeniu zestawu zadań.Ta funkcja zwraca std::vector obiekt zawierający wynik każdego zadania w zestawie.W poniższym przykładzie podstawowe when_all , aby utworzyć zadanie, które reprezentuje zakończenie trzech innych zadań.

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

[!UWAGA]

Zadania, które są przekazywane do when_all muszą być jednolite.Innymi słowy muszą one wszystkie zwracać tego samego typu.

Można również użyć && składnię do wyprodukowania kończy po zakończeniu zestawu zadań, jak pokazano w poniższym przykładzie zadanie.

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

Często używać kontynuacji razem z when_all do wykonania akcji po zakończeniu zestawu zadań.Poniższy przykład modyfikuje poprzedniego suma trzech zadań drukowania każdego produkuje int wynik.

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

W tym przykładzie można również określić task<vector<int>> do kontynuacji opartych na zadaniach produkcji.

Informacje dotyczące przestrogiPrzestroga

Jeśli dowolne zadanie zestawu zadań zostało anulowane lub generuje wyjątek, when_all natychmiast kończy, a nie czeka na zakończenie pozostałych zadań.Jeśli wyjątek runtime rethrows wyjątek podczas wywołania task::get lub task::wait na task obiekt, który when_all zwraca.Jeśli generuje więcej niż jednego zadania, runtime wybiera jeden z nich.Dlatego jeśli jeden zgłasza wyjątek, zapewnienia poczekaj wszystkie zadania do wykonania.

Top

Dd492427.collapse_all(pl-pl,VS.110).gifFunkcja when_any

when_any Funkcja tworzy zadanie kończy się po zakończeniu pierwszego zadania w zestawu zadań.Ta funkcja zwraca std::pair obiekt zawierający wynik zakończone zadanie i indeks zadania w zestawie.

when_any Funkcja jest szczególnie przydatne w następujących scenariuszach:

  • Operacje nadmiarowe.Należy rozważyć algorytmu lub operacji, które można wykonać na wiele sposobów.Można użyć when_any funkcji, aby wybrać najpierw zakończy operację i anulowanie pozostałych operacji.

  • Operacje z przeplotem.Można uruchomić wiele operacji musi wszystkie Zakończ i używać when_any funkcji przetwarzania wyników zakończeniu każdej operacji.Po zakończeniu jednej operacji można uruchomić jedną lub więcej dodatkowych zadań.

  • Ograniczanej operacji.Można użyć when_any funkcji rozszerzyć w poprzednim scenariuszu, ograniczając liczbę jednoczesnych operacji.

  • Wygasłe operacji.Można użyć when_any funkcji wybierz między jedną lub więcej zadań i zadań, który zakończy się po określonej godzinie.

Tak jak w when_all, często używać kontynuacji, który ma when_any do wykonania akcji po zakończeniu pierwszego zestawu zadań.W poniższym przykładzie podstawowe when_any utworzyć zadanie kończy się po zakończeniu pierwszego trzech innych zadań.

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

W tym przykładzie można również określić task<pair<int, size_t>> do kontynuacji opartych na zadaniach produkcji.

[!UWAGA]

Tak jak w when_all, zadania, które są przekazywane do when_any musi zwracać wszystkie tego samego typu.

Można również użyć || składnię do wyprodukowania kończy po zakończeniu pierwszego zadania w zestaw zadań, jak pokazano w poniższym przykładzie zadanie.

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

Top

Wykonanie zadania opóźnione

Czasami konieczne jest, aby opóźnić wykonanie zadania, dopóki warunek jest spełniony lub uruchomić zadanie w odpowiedzi na zdarzenie zewnętrzne.Na przykład asynchronicznego programowania można mieć uruchamiają zadanie w odpowiedzi na zdarzenie zakończenia We/Wy.

Są dwa sposoby osiągnięcia tego użyć kontynuacji lub do uruchamiania zadania i oczekiwania na zdarzenia wewnątrz funkcji pracy zadania.Jednak istnieją przypadki, gdy go nie jest możliwe użycie jednej z tych technik.Na przykład aby utworzyć kontynuacji, musi mieć zadania antecedent.Jednakże, jeśli nie masz antecedent zadań, można utworzyć zdarzenia zakończenia zadania i zdarzenia zakończenia zadania antecedent łańcucha później, kiedy stają się dostępne.Ponadto ponieważ zadań oczekujących blokuje również wątku, można użyć zdarzenia zakończenia zadania do wykonania pracy, gdy zakończy operację asynchroniczną, a zatem bez wątku.

Concurrency::task_completion_event klasy pomaga uprościć skład takich zadań.Jak task klasy parametr typu T jest typem wynik, który jest wytwarzany przez zadanie.Tego typu może być void , jeśli zadanie nie zwraca wartości.Tnie można użyć const modyfikator.Zazwyczaj task_completion_event obiektu jest dostarczone do wątku lub zadanie, które będzie ona sygnału, wtedy gdy wartość dla niego dostępne.W tym samym czasie jeden lub więcej zadań są ustawiane jako detektory zdarzenia.Gdy zdarzenie ukończenia zadania odbiornika i zaplanowane ich kontynuacji.

Na przykład, który używa task_completion_event do wykonania zadania zakończy z opóźnieniem, zobacz Jak: Tworzenie zadania kończąca z opóźnieniem.

Top

Grupy zadań

A grupy zadań organizuje zbiorem zadań.Grupy zadań push zadań do kolejki kradzież pracy.Harmonogram usuwa z tej kolejki zadań i wykonuje je na dostępnych zasobów komputerowych.Po dodaniu do grupy zadań zadania, można poczekać na wszystkie zadania Zakończ lub Anuluj zadania, które nie zostały jeszcze uruchomiona.

Używa PPL concurrency::task_group i concurrency::structured_task_group klasy do reprezentowania grupy zadań i concurrency::task_handle klasy do reprezentowania zadania, które będą uruchamiane w tych grupach.task_handle Klasy hermetyzuje kod wykonujący pracę.Jak task klasy, funkcja pracę pochodzi w postaci funkcji lambda, wskaźnik funkcji lub funkcji obiekt.Zazwyczaj nie trzeba pracować z task_handle obiektów bezpośrednio.Należy przekazać funkcje pracy do grupy zadań, i grupy zadań tworzy i zarządza task_handle obiektów.

PPL dzieli grup zadań na te dwie kategorie: grup zadań niestrukturalnych i grup zadań w strukturze.Używa PPL task_group klasy do reprezentowania grupy zadań niestrukturalnych i structured_task_group klasy do reprezentowania grupy wykonujący zadania strukturalne.

Ważna uwagaWażne

Definiuje również PPL concurrency::parallel_invoke algorytm, który używa structured_task_group klasy do wykonania zestawu zadań równolegle.Ponieważ parallel_invoke algorytm ma bardziej zwięzłe składnię, zaleca się używać go structured_task_group klasy, gdy można.Temat Algorytmy równoległe opisuje parallel_invoke bardziej szczegółowo.

Użyj parallel_invoke kiedy masz kilka niezależnych zadania, które chcesz wykonać jednocześnie i należy poczekać na zakończenie przed kontynuowaniem wszystkich zadań.Ta technika jest często określane jako rozwidlenia i sprzężenia równoległości.Użyj task_group kiedy masz kilka niezależnych zadania, które chcesz wykonać w tym samym czasie, ale chcesz poczekać na zakończenie w późniejszym czasie zadań.Na przykład można dodawać zadania do task_group obiektu i poczekaj na zakończenie w innej funkcji lub z innego wątku zadań.

Grupy zadań wsparcie koncepcji anulowania.Umożliwia anulowanie sygnału wszystkie aktywne zadania chcesz anulować operację ogólnej.Anulowanie uniemożliwia również zadania, które nie zaczęły jeszcze uruchamianiu.Więcej informacji o unieważnieniu, zobacz Anulowanie w PPL.

Środowisko wykonawcze udostępnia model obsługi wyjątków, umożliwiający Zgłoś wyjątek, zadania i obsłużyć ten wyjątek podczas oczekiwania dla grupy skojarzonego zadania do zakończenia.Aby uzyskać więcej informacji na temat modelu obsługi wyjątków, zobacz Obsługa wyjątków w Runtime współbieżności.

Top

Porównanie task_group do structured_task_group

Chociaż firma Microsoft zaleca użycie task_group lub parallel_invoke zamiast structured_task_group klasy, istnieją przypadki, gdy chcesz użyć structured_task_group, na przykład, kiedy napisać równoległych algorytm, który wykonuje zmienna liczba zadań lub wymaga obsługi anulowania.W tej sekcji wyjaśniono różnice między task_group i structured_task_group klasy.

task_group Klasy jest wielowątkowość.Dlatego można dodawać zadania do task_group obiekt z wielu wątków i poczekaj lub Anuluj task_group obiekt z wielu wątków.Budowa i niszczenia structured_task_group obiektu musi nastąpić w tym samym zakresie leksykalne.Dodatkowo, wszystkie operacje na structured_task_group obiektu musi nastąpić w tym samym wątku.Wyjątkiem od tej reguły jest concurrency::structured_task_group::cancel i concurrency::structured_task_group::is_canceling metody.Zadania podrzędne mogą wywoływać te metody, aby anulować nadrzędnego grupy zadań lub anulowaniu w dowolnym czasie sprawdzić.

Można wykonać dodatkowe zadania na task_group obiektu po wywołaniu concurrency::task_group::wait lub concurrency::task_group::run_and_wait metody.Odwrotnie, nie można uruchomić zadania dodatkowe, na structured_task_group obiektu po wywołaniu concurrency::structured_task_group::wait lub concurrency::structured_task_group::run_and_wait metody.

Ponieważ structured_task_group klasy nie synchronizowane między wątkami, posiada mniej wykonanie obciążenie niż task_group klasy.Dlatego, jeśli problem nie wymagają, aby zaplanować pracę z wieloma wątkami i nie można używać parallel_invoke algorytm, structured_task_group klasy może pomóc pisać lepsze wykonywanie kodu.

Jeśli używasz jednego structured_task_group obiektu w innej structured_task_group obiektu, obiekt wewnętrzny musi zakończyć i zniszczone przed zakończeniem zewnętrznego obiektu.task_group Klasy nie wymaga dla grup zagnieżdżonych zadań do zakończenia przed zakończeniem grupy zewnętrznej.

Grupy zadań niestrukturalnych i grupy wykonujący zadania strukturalne pracy z uchwytami zadania na różne sposoby.Funkcje pracy można przekazać bezpośrednio do task_group obiektu; task_group obiektu będzie utworzyć i zarządzać uchwyt zadania.structured_task_group Klasy wymaga zarządzania task_handle obiektu dla każdego zadania.Każdy task_handle obiektu pozostają ważne w okresie istnienia jego skojarzony structured_task_group obiektu.Użyj concurrency::make_task funkcja tworzenia task_handle obiektów, jak pokazano w poniższym przykładzie podstawowe:

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

Zarządzanie uchwyty zadań w przypadkach, gdy mają zmienna liczba zadań, użyć procedury Alokacja stosu takich jak _malloca lub kontenera klasy, takie jak std::vector.

Obie task_group i structured_task_group obsługuje anulowania.Więcej informacji o unieważnieniu, zobacz Anulowanie w PPL.

Top

Przykład

Następujący prosty przykład pokazuje sposób pracy z grupy zadań.W tym przykładzie parallel_invoke algorytm do wykonywania dwóch zadań jednocześnie.Każde zadanie dodaje podzadania task_group obiektu.Należy zauważyć, że task_group umożliwia klasy dla wielu zadań jednocześnie dodawać zadania do niej.

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

Poniżej przedstawiono przykładowe dane wyjściowe tego przykładu:

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

Ponieważ parallel_invoke algorytm działa zadań jednocześnie, kolejność wiadomości dane wyjściowe mogą być różne.

Kompletne przykłady, które pokazują, jak używać parallel_invoke algorytm, zobacz Jak: za pomocą parallel_invoke zapisu równoległych rutynowych sortowania i Jak: Użyj parallel_invoke do wykonywania operacji równoległych.Pełny przykład używający task_group klasy wdrożenia prognoz asynchronicznych, zobacz Instruktaż: Wykonawczych prognoz.

Top

Stabilne Programowanie

Upewnij się, że rozumiesz roli anulowania i obsługi wyjątków, korzystając z zadania, grup zadań i algorytmy równoległe.Na przykład w drzewie równoległych pracy jest anulowane zadania zapobiega zadania podrzędne uruchamianiu.Może to powodować problemy, jeśli jedno z zadań podrzędnych wykonuje operację, ważnych aplikacji, na przykład zwalniania zasób.Ponadto jeśli zadania podrzędne zgłasza wyjątek, ten wyjątek może propagować przez destruktor obiektu i powodują zachowanie niezdefiniowane w aplikacji.Przykład ilustruje te punkty, zobacz Understand how Cancellation and Exception Handling Affect Object Destruction sekcji najlepszych praktyk w dokumencie równoległych biblioteki wzorców.Aby uzyskać więcej informacji dotyczących anulowania i modele obsługi wyjątków w PPL, zobacz Anulowanie w PPL i Obsługa wyjątków w Runtime współbieżności.

Top

Tematy pokrewne

Tytuł

Opis

Jak: za pomocą parallel_invoke zapisu równoległych rutynowych sortowania

Pokazuje, jak użyć parallel_invoke algorytm, aby zwiększyć wydajność bitonic algorytm sortowania.

Jak: Użyj parallel_invoke do wykonywania operacji równoległych

Pokazuje, jak użyć parallel_invoke algorytm, aby zwiększyć wydajność programu, który wykonuje wiele operacji na źródle danych udostępnionych.

Jak: Tworzenie zadania kończąca z opóźnieniem

Pokazuje, jak użyć task, cancellation_token_source, cancellation_token, i task_completion_event klasy, aby utworzyć zadanie zakończy z opóźnieniem.

Instruktaż: Wykonawczych prognoz

Pokazuje, jak połączyć istniejące funkcje w czasie wykonywania współbieżności na coś, co więcej.

Biblioteka desenie równoległe (PPL)

Opisuje PPL, która przewiduje opracowywanie aplikacji równoczesnych nadrzędnych modelu programowania.

Odwołanie

zadanie klasy (współbieżności Runtime)

Klasa task_completion_event

Funkcja when_all

Funkcja when_any

Klasa task_group

Funkcja parallel_invoke

Klasa structured_task_group