Параллелизм задач (среда выполнения с параллелизмом)Task Parallelism (Concurrency Runtime)

В среда выполнения с параллелизмом задача — это единица работы, которая выполняет определенное задание и обычно выполняется параллельно с другими задачами.In the Concurrency Runtime, a task is a unit of work that performs a specific job and typically runs in parallel with other tasks. Задачу можно разложить на дополнительные, более детализированные задачи, упорядоченные в группу задач.A task can be decomposed into additional, more fine-grained tasks that are organized into a task group.

Задачи используются, когда при создании асинхронного кода требуется, чтобы после завершения асинхронной операции выполнялись некоторые операции.You use tasks when you write asynchronous code and want some operation to occur after the asynchronous operation completes. Например, можно использовать задачу для асинхронного считывания из файла, а затем использовать другую задачу — задачу продолжения, которая описана далее в этом документе, для обработки данных после того, как она станет доступной.For example, you could use a task to asynchronously read from a file and then use another task—a continuation task, which is explained later in this document—to process the data after it becomes available. И наоборот, можно использовать группы задач для разбиения параллельной работы на более мелкие части.Conversely, you can use tasks groups to decompose parallel work into smaller pieces. Например, предположим, что имеется рекурсивный алгоритм, разделяющий оставшуюся работу на два раздела.For example, suppose you have a recursive algorithm that divides the remaining work into two partitions. С помощью групп задач вы можете одновременно запустить эти разделы, а затем подождать, пока выполнится эта разделенная задача.You can use task groups to run these partitions concurrently, and then wait for the divided work to complete.

Совет

Если необходимо применить одну и ту же подпрограммы к каждому элементу коллекции параллельно, используйте параллельный алгоритм, например Concurrency::p arallel_for, а не задачу или группу задач.When you want to apply the same routine to every element of a collection in parallel, use a parallel algorithm, such as concurrency::parallel_for, instead of a task or task group. Дополнительные сведения о параллельных алгоритмах см. в разделе Параллельные алгоритмы.For more information about parallel algorithms, see Parallel Algorithms.

Основные моментыKey Points

  • При передаче переменных в лямбда-выражение по ссылке необходимо обеспечить сохранение существования этой переменной до завершения задачи.When you pass variables to a lambda expression by reference, you must guarantee that the lifetime of that variable persists until the task finishes.

  • Используйте задачи (класс Concurrency:: Task ) при написании асинхронного кода.Use tasks (the concurrency::task class) when you write asynchronous code. Класс задач использует в качестве своего планировщика Windows ThreadPool (пул потоков Windows), а не среду выполнения с параллелизмом.The task class uses the Windows ThreadPool as its scheduler, not the Concurrency Runtime.

  • Используйте группы задач (класс Concurrency:: task_group или метод concurrency::p arallel_invoke алгоритм), если необходимо разложить параллельную работу на небольшие части, а затем дождаться завершения этих меньших частей.Use task groups (the concurrency::task_group class or the concurrency::parallel_invoke algorithm) when you want to decompose parallel work into smaller pieces and then wait for those smaller pieces to complete.

  • Для создания продолжений используйте метод Concurrency:: Task:: then .Use the concurrency::task::then method to create continuations. Продолжение — это задача, которая выполняется асинхронно после завершения другой задачи.A continuation is a task that runs asynchronously after another task completes. Вы можете подключать любое количество продолжений для формирования цепочки асинхронной работы.You can connect any number of continuations to form a chain of asynchronous work.

  • Продолжение на основе задачи всегда планируется для выполнения после завершения предшествующей задачи, даже если предшествующая задача отменяется или создает исключение.A task-based continuation is always scheduled for execution when the antecedent task finishes, even when the antecedent task is canceled or throws an exception.

  • Используйте Concurrency:: when_all , чтобы создать задачу, которая завершается после завершения каждого члена набора задач.Use concurrency::when_all to create a task that completes after every member of a set of tasks completes. Используйте Concurrency:: when_any , чтобы создать задачу, которая завершается после завершения одного члена набора задач.Use concurrency::when_any to create a task that completes after one member of a set of tasks completes.

  • Задачи и группы задач могут участвовать в механизме отмены библиотеки параллельных шаблонов (PPL).Tasks and task groups can participate in the Parallel Patterns Library (PPL) cancellation mechanism. Дополнительные сведения см. в разделе об отмене в PPL.For more information, see Cancellation in the PPL.

  • Сведения о том, как среда выполнения обрабатывает исключения, создаваемые задачами и группами задач, см. в разделе обработка исключений.To learn how the runtime handles exceptions that are thrown by tasks and task groups, see Exception Handling.

В этом документеIn this Document

Использование лямбда-выраженийUsing Lambda Expressions

Благодаря их лаконичному синтаксису лямбда-выражения часто используют для определения операций, выполняемых задачами и группами задач.Because of their succinct syntax, lambda expressions are a common way to define the work that is performed by tasks and task groups. Ниже приведены некоторые советы по использованию.Here are some usage tips:

  • Поскольку задачи обычно выполняются в фоновых потоках, помните о времени существования объекта при включении переменных в лямбда-выражения.Because tasks typically run on background threads, be aware of the object lifetime when you capture variables in lambda expressions. При вводе переменной по значению в тексте лямбда-выражения создается копия этой переменной.When you capture a variable by value, a copy of that variable is made in the lambda body. При вводе по ссылке копия не создается.When you capture by reference, a copy is not made. Следовательно, необходимо убедиться, что время существования любой введенной по ссылке переменной превышает время существования задачи, которая ее использует.Therefore, ensure that the lifetime of any variable that you capture by reference outlives the task that uses it.

  • При передаче лямбда-выражения задаче не следует захватывать переменные, выделенные в стеке по ссылке.When you pass a lambda expression to a task, don't capture variables that are allocated on the stack by reference.

  • Должны быть явными для переменных, захваченных в лямбда-выражениях, чтобы можно было определить, какие данные записываются по значению, а по ссылке.Be explicit about the variables you capture in lambda expressions so that you can identify what you're capturing by value versus by reference. По этой причине рекомендуется не использовать параметры [=] или [&] для лямбда-выражений.For this reason we recommend that you do not use the [=] or [&] options for lambda expressions.

Распространенный подход заключается в том, что одна задача в цепочке продолжения назначает переменную, а другая задача читает эту переменную.A common pattern is when one task in a continuation chain assigns to a variable, and another task reads that variable. Вы не можете записать по значению, так как каждая задача продолжения будет содержать другую копию переменной.You can't capture by value because each continuation task would hold a different copy of the variable. Для переменных, выделенных в стеке, вы также не можете записать по ссылке, так как переменная может быть недействительной.For stack-allocated variables, you also can't capture by reference because the variable may no longer be valid.

Чтобы решить эту проблему, используйте интеллектуальный указатель, например std:: shared_ptr, чтобы заключить переменную и передать смарт-указатель по значению.To solve this problem, use a smart pointer, such as std::shared_ptr, to wrap the variable and pass the smart pointer by value. Таким образом этот базовый объект можно назначать и читать, и срок его существования будет превышать срок существования задач, которые его используют.In this way, the underlying object can be assigned to and read from, and will outlive the tasks that use it. Используйте этот метод даже в том случае, если переменная является указателем или дескриптором с подсчетом ссылок (^) объекта среды выполнения Windows.Use this technique even when the variable is a pointer or a reference-counted handle (^) to a Windows Runtime object. Простой пример:Here's a basic example:

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

Дополнительные сведения о лямбда-выражениях см. в разделе лямбда-выражения.For more information about lambda expressions, see Lambda Expressions.

Класс TaskThe task Class

Для составления задач в набор зависимых операций можно использовать класс Concurrency:: Task .You can use the concurrency::task class to compose tasks into a set of dependent operations. Эта модель композиции поддерживается понятием продолжений.This composition model is supported by the notion of continuations. Продолжение позволяет выполнять код при завершении предыдущей или предшествующей задачи.A continuation enables code to be executed when the previous, or antecedent, task completes. Результат предшествующей задачи передается в качестве входных данных в одну или несколько задач продолжения.The result of the antecedent task is passed as the input to the one or more continuation tasks. По завершении предшествующей задачи все ожидающие ее задачи продолжения планируются для выполнения.When an antecedent task completes, any continuation tasks that are waiting on it are scheduled for execution. Каждая задача продолжения получает копию результатов предшествующей задачи.Each continuation task receives a copy of the result of the antecedent task. В свою очередь, эти задачи продолжения также могут быть предшествующими задачами для других продолжений, тем самым создавая цепочки задач.In turn, those continuation tasks may also be antecedent tasks for other continuations, thereby creating a chain of tasks. Продолжения помогают создавать цепочки задач произвольной длины с определенными зависимостями между входящими в них задачами.Continuations help you create arbitrary-length chains of tasks that have specific dependencies among them. Кроме того, задача может участвовать в отмене либо до запуска, либо совместно во время выполнения.In addition, a task can participate in cancellation either before a tasks starts or in a cooperative manner while it is running. Дополнительные сведения об этой модели отмены см. в разделе Отмена в библиотеке PPL.For more information about this cancellation model, see Cancellation in the PPL.

task является классом шаблона.task is a template class. Параметр типа T — это тип результата, созданного задачей.The type parameter T is the type of the result that is produced by the task. Этот тип может быть void , если задача не возвращает значение.This type can be void if the task does not return a value. T нельзя использовать const модификатор.T cannot use the const modifier.

При создании задачи вы предоставляете рабочую функцию , которая выполняет тело задачи.When you create a task, you provide a work function that performs the task body. Эта рабочая функция поступает в виде лямбда-функции, указателя функции или объекта функции.This work function comes in the form of a lambda function, function pointer, or function object. Чтобы дождаться завершения задачи без получения результата, вызовите метод Concurrency:: Task:: wait .To wait for a task to finish without obtaining the result, call the concurrency::task::wait method. task::waitМетод возвращает значение Concurrency:: task_status , которое описывает, была ли задача завершена или отменена.The task::wait method returns a concurrency::task_status value that describes whether the task was completed or canceled. Чтобы получить результат задачи, вызовите метод Concurrency:: Task:: Get .To get the result of the task, call the concurrency::task::get method. Этот метод вызывает метод task::wait для ожидания завершения задачи и таким образом блокирует выполнение текущего потока, пока не станет доступен результат.This method calls task::wait to wait for the task to finish, and therefore blocks execution of the current thread until the result is available.

В следующем примере показано, как создать задачу, дождаться ее результата и отобразить полученное значение.The following example shows how to create a task, wait for its result, and display its value. В примерах в этой документации используются лямбда-функции, поскольку они обеспечивают более лаконичный синтаксис.The examples in this documentation use lambda functions because they provide a more succinct syntax. Однако при использовании задач вы также можете применять указатели функций и объекты функций.However, you can also use function pointers and function objects when you use tasks.

// 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 можно использовать auto ключевое слово вместо объявления типа.When you use the concurrency::create_task function, you can use the auto keyword instead of declaring the type. Например, рассмотрим следующий код, который создает и печатает матрицу тождественности.For example, consider this code that creates and prints the identity matrix:

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

Вы можете использовать функцию create_task для создания эквивалентной операции.You can use the create_task function to create the equivalent operation.

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

Если во время выполнения задачи возникает исключение, среда выполнения маршалирует исключение в последующий вызов метода task::get или task::wait, или в продолжение на основе задачи.If an exception is thrown during the execution of a task, the runtime marshals that exception in the subsequent call to task::get or task::wait, or to a task-based continuation. Дополнительные сведения о механизме обработки исключений задач см. в разделе обработка исключений.For more information about the task exception-handling mechanism, see Exception Handling.

Пример, в котором используется метод task , Concurrency:: task_completion_event, Отмена, см. в разделе Пошаговое руководство. подключение с использованием задач и HTTP-запросов XML.For an example that uses task, concurrency::task_completion_event, cancellation, see Walkthrough: Connecting Using Tasks and XML HTTP Requests. (Класс task_completion_event описывается далее в этом документе.)(The task_completion_event class is described later in this document.)

Совет

Дополнительные сведения о задачах в приложениях UWP см. в статье Асинхронное программирование на c++ и Создание асинхронных операций в C++ для приложений UWP.To learn details that are specific to tasks in UWP apps, see Asynchronous programming in C++ and Creating Asynchronous Operations in C++ for UWP Apps.

Задачи продолженияContinuation Tasks

В асинхронном программировании очень распространено при завершении одной асинхронной операции вызывать вторую операцию и передавать в нее данные.In asynchronous programming, it is very common for one asynchronous operation, on completion, to invoke a second operation and pass data to it. Как правило, это делается с помощью методов обратного вызова.Traditionally, this is done by using callback methods. В среда выполнения с параллелизмом те же функциональные возможности предоставляются задачами продолжения.In the Concurrency Runtime, the same functionality is provided by continuation tasks. Задача продолжения (также известная как продолжение) — это асинхронная задача, которая вызывается другой задачей, которая называется предшествующей, после завершения предшествующей задачи.A continuation task (also known just as a continuation) is an asynchronous task that is invoked by another task, which is known as the antecedent, when the antecedent completes. С помощью продолжений вы можете делать следующее.By using continuations, you can:

  • Передавать данные из предшествующей задачи в продолжение.Pass data from the antecedent to the continuation.

  • Указывать точные условия, при которых продолжение вызывается или не вызывается.Specify the precise conditions under which the continuation is invoked or not invoked.

  • Отменять продолжение перед его запуском либо совместно во время его выполнения.Cancel a continuation either before it starts or cooperatively while it is running.

  • Определять подсказки, как должно планироваться продолжение.Provide hints about how the continuation should be scheduled. (Это относится только к приложениям универсальная платформа Windows (UWP).(This applies to Universal Windows Platform (UWP) apps only. Дополнительные сведения см. в статье Создание асинхронных операций в C++ для приложений UWP.)For more information, see Creating Asynchronous Operations in C++ for UWP Apps.)

  • Вызывать несколько продолжений из одной и той же предшествующей задачи.Invoke multiple continuations from the same antecedent.

  • Вызывать одно продолжение по завершении всех или одной из нескольких предшествующих задач.Invoke one continuation when all or any of multiple antecedents complete.

  • Прикреплять продолжения одно после другого до любой длины.Chain continuations one after another to any length.

  • Использовать продолжение для обработки исключений, вызванных предшествующей задачей.Use a continuation to handle exceptions that are thrown by the antecedent.

Эти возможности позволяют выполнять одну или несколько задач после завершения первой задачи.These features enable you to execute one or more tasks when the first task completes. Например, можно создать продолжение, которое сжимает файл после того, как первая задача прочитает этот файл с диска.For example, you can create a continuation that compresses a file after the first task reads it from disk.

Следующий пример изменяет предыдущий, чтобы использовать метод Concurrency:: Task:: then , чтобы запланировать продолжение, которое выводит значение предшествующей задачи, когда она доступна.The following example modifies the previous one to use the concurrency::task::then method to schedule a continuation that prints the value of the antecedent task when it is available.

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

Вы можете прикреплять и вкладывать задачи до любой длины.You can chain and nest tasks to any length. Задача также может иметь несколько продолжений.A task can also have multiple continuations. В следующем примере демонстрируется базовая цепочка продолжений, которая увеличивает значение предыдущей задачи три раза.The following example illustrates a basic continuation chain that increments the value of the previous task three times.

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

Продолжение может также возвращать другую задачу.A continuation can also return another task. Если отмена отсутствует, то эта задача выполняется до последующего продолжения.If there is no cancellation, then this task is executed before the subsequent continuation. Этот прием называется асинхронным распаковкой.This technique is known as asynchronous unwrapping. Асинхронное развертывание удобно использовать, когда требуется выполнить дополнительную работу в фоновом режиме, но так, чтобы текущая задача не блокировала текущий поток.Asynchronous unwrapping is useful when you want to perform additional work in the background, but do not want the current task to block the current thread. (Это распространено в приложениях UWP, где продолжение может выполняться в потоке пользовательского интерфейса).(This is common in UWP apps, where continuations can run on the UI thread). В следующем примере показаны три задачи.The following example shows three tasks. Первая задача возвращает вторую задачу, которая выполняется перед задачей продолжения.The first task returns another task that is run before a continuation task.

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

Важно!

Когда продолжение задачи возвращает вложенную задачу типа N, результирующая задача имеет тип N, а не task<N>, и завершается при завершении вложенной задачи.When a continuation of a task returns a nested task of type N, the resulting task has the type N, not task<N>, and completes when the nested task completes. Другими словами, продолжение выполняет развертывание вложенной задачи.In other words, the continuation performs the unwrapping of the nested task.

Value-Based и Task-Based продолженийValue-Based Versus Task-Based Continuations

Принимая во внимание объект task, который имеет возвращаемый тип T, вы можете предоставить значение типа T или task<T> в соответствующие задачи продолжения.Given a task object whose return type is T, you can provide a value of type T or task<T> to its continuation tasks. Продолжение, принимающее тип, T называется продолжением на основе значения.A continuation that takes type T is known as a value-based continuation. Продолжение на основе значения планируется для выполнения, когда предшествующая задача завершается без ошибок и не отменяется.A value-based continuation is scheduled for execution when the antecedent task completes without error and is not canceled. Продолжение, принимающее тип task<T> в качестве параметра, называется продолжением на основе задачи.A continuation that takes type task<T> as its parameter is known as a task-based continuation. Продолжение на основе задачи всегда планируется для выполнения после завершения предшествующей задачи, даже если предшествующая задача отменяется или создает исключение.A task-based continuation is always scheduled for execution when the antecedent task finishes, even when the antecedent task is canceled or throws an exception. Затем можно вызвать task::get, чтобы получить результат предшествующей задачи.You can then call task::get to get the result of the antecedent task. Если предшествующая задача была отменена, task::get вызывается метод Concurrency:: task_canceled.If the antecedent task was canceled, task::get throws concurrency::task_canceled. Если предшествующая задача выдала исключение, task::get повторно выдает это исключение.If the antecedent task threw an exception, task::get rethrows that exception. Продолжение на основе задачи не отмечается как отмененное, когда отменяется предшествующая задача.A task-based continuation is not marked as canceled when its antecedent task is canceled.

Составление задачComposing Tasks

В этом разделе описываются функции Concurrency:: when_all и concurrency:: when_any , которые могут помочь составить несколько задач для реализации общих шаблонов.This section describes the concurrency::when_all and concurrency::when_any functions, which can help you compose multiple tasks to implement common patterns.

Функция when_allThe when_all Function

Функция when_all создает задачу, которая выполняется после завершения набора задач.The when_all function produces a task that completes after a set of tasks complete. Эта функция возвращает объект std::vector , который содержит результат каждой задачи в наборе.This function returns a std::vector object that contains the result of each task in the set. В следующем базовом примере функция when_all используется для создания задачи, которая представляет завершение трех других задач.The following basic example uses when_all to create a task that represents the completion of three other tasks.

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

Примечание

Задачи, которые вы передаете в функцию when_all, должны быть единообразными.The tasks that you pass to when_all must be uniform. Другими словами, все они должны возвращать один и тот же тип.In other words, they must all return the same type.

Для создания задачи, выполняемой после завершения набора задач, можно также использовать синтаксис &&, как показано в следующем примере.You can also use the && syntax to produce a task that completes after a set of tasks complete, as shown in the following example.

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

Обычно для выполнения действия после завершения набора задач используется продолжение вместе с функцией when_all.It is common to use a continuation together with when_all to perform an action after a set of tasks finishes. Следующий пример изменяет предыдущий, чтобы напечатать сумму трех задач, каждый из которых формирует int результат.The following example modifies the previous one to print the sum of three tasks that each produce an int result.

// Observes all exceptions that occurred in all tasks in the given range.
template<class T, class InIt> 
void observe_all_exceptions(InIt first, InIt last) 
{
    std::for_each(first, last, [](concurrency::task<T> t)
    {
        t.then([](concurrency::task<T> previousTask)
        {
            try
            {
                previousTask.get();
            }
            // Although you could catch (...), this demonstrates how to catch specific exceptions. Your app
            // might handle different exception types in different ways.
            catch (Platform::Exception^)
            {
                // Swallow the exception.
            }
            catch (const std::exception&)
            {
                // Swallow the exception.
            }
        });
    });
}

В этом примере можно также указать task<vector<int>>, чтобы создать продолжение на основе задачи.In this example, you can also specify task<vector<int>> to produce a task-based continuation.

Если какая-либо из задач в наборе задач отменяется или порождает исключение, when_all немедленно завершается и не ждет завершения выполнения оставшихся задач.If any task in a set of tasks is canceled or throws an exception, when_all immediately completes and does not wait for the remaining tasks to finish. Если выдается исключение, среда выполнения повторно выдает это исключение при вызове task::get или task::wait в объекте задачи, который возвращает when_all.If an exception is thrown, the runtime rethrows the exception when you call task::get or task::wait on the task object that when_all returns. Если исключение выдают несколько задач, среда выполнения выбирает одну из них.If more than one task throws, the runtime chooses one of them. Поэтому убедитесь, что вы заметили все исключения после завершения всех задач; необработанное исключение задачи приведет к завершению работы приложения.Therefore, ensure that you observe all exceptions after all tasks complete; an unhandled task exception causes the app to terminate.

Ниже приведена служебная функция, которую можно использовать, чтобы убедиться, что программа отслеживает все исключения.Here's a utility function that you can use to ensure that your program observes all exceptions. Для каждой задачи в указанном диапазоне эта служебная функция observe_all_exceptions запускает повторную выдачу каждого возникшего исключения, а затем поглощает это исключение.For each task in the provided range, observe_all_exceptions triggers any exception that occurred to be rethrown and then swallows that exception.

// Writes content to files in the provided storage folder.
// The first element in each pair is the file name. The second element holds the file contents.
task<void> MainPage::WriteFilesAsync(StorageFolder^ folder, const vector<pair<String^, String^>>& fileContents)
{
    // For each file, create a task chain that creates the file and then writes content to it. Then add the task chain to a vector of tasks.
    vector<task<void>> tasks;
    for (auto fileContent : fileContents)
    {
        auto fileName = fileContent.first;
        auto content = fileContent.second;

        // Create the file. The CreationCollisionOption::FailIfExists flag specifies to fail if the file already exists.
        tasks.emplace_back(create_task(folder->CreateFileAsync(fileName, CreationCollisionOption::FailIfExists)).then([content](StorageFile^ file)
        {
            // Write its contents.
            return create_task(FileIO::WriteTextAsync(file, content));
        }));
    }

    // When all tasks finish, create a continuation task that observes any exceptions that occurred.
    return when_all(begin(tasks), end(tasks)).then([tasks](task<void> previousTask)
    {
        task_status status = completed;
        try
        {
            status = previousTask.wait();
        }
        catch (COMException^ e)
        {
            // We'll handle the specific errors below.
        }
        // TODO: If other exception types might happen, add catch handlers here.

        // Ensure that we observe all exceptions.
        observe_all_exceptions<void>(begin(tasks), end(tasks));

        // Cancel any continuations that occur after this task if any previous task was canceled.
        // Although cancellation is not part of this example, we recommend this pattern for cases that do.
        if (status == canceled)
        {
            cancel_current_task();
        }
    });
}

Рассмотрим приложение UWP, которое использует C++ и XAML и записывает набор файлов на диск.Consider a UWP app that uses C++ and XAML and writes a set of files to disk. В следующем примере показано, как использовать функции when_all и observe_all_exceptions, чтобы убедиться, что программа обнаруживает все исключения.The following example shows how to use when_all and observe_all_exceptions to ensure that the program observes all exceptions.

// Writes content to files in the provided storage folder.
// The first element in each pair is the file name. The second element holds the file contents.
task<void> MainPage::WriteFilesAsync(StorageFolder^ folder, const vector<pair<String^, String^>>& fileContents)
{
    // For each file, create a task chain that creates the file and then writes content to it. Then add the task chain to a vector of tasks.
    vector<task<void>> tasks;
    for (auto fileContent : fileContents)
    {
        auto fileName = fileContent.first;
        auto content = fileContent.second;

        // Create the file. The CreationCollisionOption::FailIfExists flag specifies to fail if the file already exists.
        tasks.emplace_back(create_task(folder->CreateFileAsync(fileName, CreationCollisionOption::FailIfExists)).then([content](StorageFile^ file)
        {
            // Write its contents.
            return create_task(FileIO::WriteTextAsync(file, content));
        }));
    }

    // When all tasks finish, create a continuation task that observes any exceptions that occurred.
    return when_all(begin(tasks), end(tasks)).then([tasks](task<void> previousTask)
    {
        task_status status = completed;
        try
        {
            status = previousTask.wait();
        }
        catch (COMException^ e)
        {
            // We'll handle the specific errors below.
        }
        // TODO: If other exception types might happen, add catch handlers here.

        // Ensure that we observe all exceptions.
        observe_all_exceptions<void>(begin(tasks), end(tasks));

        // Cancel any continuations that occur after this task if any previous task was canceled.
        // Although cancellation is not part of this example, we recommend this pattern for cases that do.
        if (status == canceled)
        {
            cancel_current_task();
        }
    });
}
Запуск примераTo run this example
  1. Добавьте в файл MainPage.xaml элемент управления Button.In MainPage.xaml, add a Button control.
<Button x:Name="Button1" Click="Button_Click">Write files</Button>
  1. В MainPage. XAML. h добавьте эти прямые объявления в private раздел MainPage объявления класса.In MainPage.xaml.h, add these forward declarations to the private section of the MainPage class declaration.
void Button_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e);
concurrency::task<void> WriteFilesAsync(Windows::Storage::StorageFolder^ folder, const std::vector<std::pair<Platform::String^, Platform::String^>>& fileContents);
  1. В MainPage.xaml.cpp реализуйте обработчик событий Button_Click.In MainPage.xaml.cpp, implement the Button_Click event handler.
// 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.
*/
  1. В MainPage.xaml.cpp реализуйте WriteFilesAsync, как показано в примере.In MainPage.xaml.cpp, implement WriteFilesAsync as shown in the example.

Совет

Функция when_all является функцией без блокировки, в качестве результата создающей task.when_all is a non-blocking function that produces a task as its result. В отличие от Task:: wait, эту функцию можно вызывать в приложении UWP в потоке ASTA (Application STA).Unlike task::wait, it is safe to call this function in a UWP app on the ASTA (Application STA) thread.

Функция when_anyThe when_any Function

Функция when_any создает задачу, которая выполняется после завершения первой задачи в наборе задач.The when_any function produces a task that completes when the first task in a set of tasks completes. Эта функция возвращает объект Air std::p , который содержит результат завершенной задачи, и индекс этой задачи в наборе.This function returns a std::pair object that contains the result of the completed task and the index of that task in the set.

Функция when_any особенно полезна в следующих ситуациях.The when_any function is especially useful in the following scenarios:

  • Избыточные операции.Redundant operations. Рассмотрим алгоритм или операцию, которые можно выполнить несколькими способами.Consider an algorithm or operation that can be performed in many ways. Функцию when_any можно использовать для выбора операции, которая завершается первой, и последующей отмены оставшихся операций.You can use the when_any function to select the operation that finishes first and then cancel the remaining operations.

  • Операции с чередованием.Interleaved operations. Можно запустить несколько операций, которые все должны завершиться, и использовать функцию when_any для обработки результатов при завершении каждой операции.You can start multiple operations that all must finish and use the when_any function to process results as each operation finishes. После завершения одной операции можно запустить одну или несколько дополнительных задач.After one operation finishes, you can start one or more additional tasks.

  • Регулируемые операции.Throttled operations. Функцию when_any можно использовать для расширения предыдущего сценария путем ограничения количества параллельных операций.You can use the when_any function to extend the previous scenario by limiting the number of concurrent operations.

  • Операции с истекшим сроком действия.Expired operations. Функцию when_any можно использовать, чтобы сделать выбор между одной или несколькими задачами и задачей, завершающейся после определенного времени.You can use the when_any function to select between one or more tasks and a task that finishes after a specific time.

Как и в случае с функцией when_all, обычно для выполнения действия после завершения первой задачи в наборе используется продолжение, имеющее функцию when_any.As with when_all, it is common to use a continuation that has when_any to perform action when the first in a set of tasks finish. В следующем базовом примере функция when_any используется для создания задачи, которая выполняется после завершения первой из трех других задач.The following basic example uses when_any to create a task that completes when the first of three other tasks completes.

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

В этом примере можно также указать task<pair<int, size_t>>, чтобы создать продолжение на основе задачи.In this example, you can also specify task<pair<int, size_t>> to produce a task-based continuation.

Примечание

Как и при использовании функции when_all, задачи, которые вы передаете в when_any, должны возвращать один и тот же тип.As with when_all, the tasks that you pass to when_any must all return the same type.

Для создания задачи, выполняемой после завершения первой задачи в наборе, можно также использовать синтаксис ||, как показано в следующем примере.You can also use the || syntax to produce a task that completes after the first task in a set of tasks completes, as shown in the following example.

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

Совет

Как и when_all , when_any не является блокирующим и является типобезопасным для вызова в приложении UWP в потоке ASTA.As with when_all, when_any is non-blocking and is safe to call in a UWP app on the ASTA thread.

Выполнение отложенных задачDelayed Task Execution

Иногда требуется отложить выполнение задачи до выполнения условия или запустить задачу в ответ на внешнее событие.It is sometimes necessary to delay the execution of a task until a condition is satisfied, or to start a task in response to an external event. Например, в асинхронном программировании может потребоваться запустить задачу в ответ на событие завершения операции ввода-вывода.For example, in asynchronous programming, you might have to start a task in response to an I/O completion event.

Это можно сделать двумя способами: использовать продолжение или для запуска задачи и ожидания события в рабочей функции задачи.Two ways to accomplish this are to use a continuation or to start a task and wait on an event inside the task's work function. Однако бывают случаи, когда невозможно использовать ни один из этих способов.However, there are cases where is it not possible to use one of these techniques. Например, чтобы создать продолжение, необходимо иметь предшествующую задачу.For example, to create a continuation, you must have the antecedent task. Однако если у вас нет предшествующей задачи, можно создать событие завершения задачи и позднее связать событие завершения с предшествующей задачей, когда она станет доступной.However, if you do not have the antecedent task, you can create a task completion event and later chain that completion event to the antecedent task when it becomes available. Кроме того, поскольку ожидающая задача также блокирует поток, можно использовать события завершения задачи для выполнения работы при завершении асинхронной операции и тем самым освободить поток.In addition, because a waiting task also blocks a thread, you can use task completion events to perform work when an asynchronous operation completes, and thereby free a thread.

Класс Concurrency:: task_completion_event упрощает такую композицию задач.The concurrency::task_completion_event class helps simplify such composition of tasks. Как и в классе task, параметр типа T — это тип результата, созданного задачей.Like the task class, the type parameter T is the type of the result that is produced by the task. Этот тип может быть void , если задача не возвращает значение.This type can be void if the task does not return a value. T нельзя использовать const модификатор.T cannot use the const modifier. Как правило, объект task_completion_event передается в поток или задачу, которые будут сообщать, когда значение для них станет доступным.Typically, a task_completion_event object is provided to a thread or task that will signal it when the value for it becomes available. В то же время одна или несколько задач устанавливаются в качестве прослушивателей этого события.At the same time, one or more tasks are set as listeners of that event. Когда событие возникает, задачи прослушивателя выполняются и их продолжения планируются для запуска.When the event is set, the listener tasks complete and their continuations are scheduled to run.

Пример использования task_completion_event для реализации задачи, которая завершается после задержки, см. в разделе как создать задачу, которая завершается после некоторой задержки.For an example that uses task_completion_event to implement a task that completes after a delay, see How to: Create a Task that Completes After a Delay.

Группы задачTask Groups

Группа задач организует коллекцию задач.A task group organizes a collection of tasks. Группы задач помещают задачи в очередь перехвата работы.Task groups push tasks on to a work-stealing queue. Планировщик удаляет задачи из этой очереди и выполняет их с использованием доступных вычислительных ресурсов.The scheduler removes tasks from this queue and executes them on available computing resources. После добавления задач в группу задач можно ожидать завершения всех задач или отменить задачи, которые еще не запускались.After you add tasks to a task group, you can wait for all tasks to finish or cancel tasks that have not yet started.

PPL использует классы Concurrency:: task_group и concurrency:: structured_task_group для представления групп задач, а также класс Concurrency:: task_handle для представления задач, выполняемых в этих группах.The PPL uses the concurrency::task_group and concurrency::structured_task_group classes to represent task groups, and the concurrency::task_handle class to represent the tasks that run in these groups. Класс task_handle инкапсулирует код, выполняющий работу.The task_handle class encapsulates the code that performs work. Как и в случае класса task, эта рабочая функция поступает в виде лямбда-функции, указателя функции или объекта функции.Like the task class, the work function comes in the form of a lambda function, function pointer, or function object. Обычно не требуется работать с объектами task_handle напрямую.You typically do not need to work with task_handle objects directly. Вместо этого вы передаете рабочие функции в группу задач, а группа задач создает объекты task_handle и управляет ими.Instead, you pass work functions to a task group, and the task group creates and manages the task_handle objects.

PPL разделяет группы задач на следующие две категории: неструктурированные группы задач и структурированные группы задач.The PPL divides task groups into these two categories: unstructured task groups and structured task groups. PPL использует класс task_group для представления неструктурированных групп задач и класс structured_task_group — для представления структурированных групп задач.The PPL uses the task_group class to represent unstructured task groups and the structured_task_group class to represent structured task groups.

Важно!

PPL также определяет алгоритм параллелизма::p arallel_invoke , который использует structured_task_group класс для параллельного выполнения набора задач.The PPL also defines the concurrency::parallel_invoke algorithm, which uses the structured_task_group class to execute a set of tasks in parallel. Поскольку алгоритм parallel_invoke имеет более лаконичный синтаксис, рекомендуется по возможности использовать его вместо класса structured_task_group.Because the parallel_invoke algorithm has a more succinct syntax, we recommend that you use it instead of the structured_task_group class when you can. Раздел Параллельные алгоритмы parallel_invoke более подробно описывается в разделе.The topic Parallel Algorithms describes parallel_invoke in greater detail.

Используйте алгоритм parallel_invoke, когда имеется несколько независимых задач, которые требуется выполнить одновременно, и перед продолжением необходимо дождаться завершения всех задач.Use parallel_invoke when you have several independent tasks that you want to execute at the same time, and you must wait for all tasks to finish before you continue. Этот метод часто называют параллелизмом разветвления и соединений .This technique is often referred to as fork and join parallelism. Используйте алгоритм task_group, когда имеется несколько независимых задач, которые требуется выполнить одновременно, но дождаться завершения всех задач требуется позднее.Use task_group when you have several independent tasks that you want to execute at the same time, but you want to wait for the tasks to finish at a later time. Например, вы можете добавить задачи в объект task_group и дожидаться завершения этих задач в другой функции или из другого потока.For example, you can add tasks to a task_group object and wait for the tasks to finish in another function or from another thread.

Группы задач поддерживают принцип отмены.Task groups support the concept of cancellation. Отмена позволяет сообщить всем активным задачам, что необходимо отменить всю операцию.Cancellation enables you to signal to all active tasks that you want to cancel the overall operation. Отмена также предотвращает запуск задач, которые еще не начали выполняться.Cancellation also prevents tasks that have not yet started from starting. Дополнительные сведения об отмене см. в разделе Отмена в библиотеке PPL.For more information about cancellation, see Cancellation in the PPL.

Среда выполнения также предоставляет модель обработки исключений, которая позволяет вызывать исключение из задачи и обработать это исключение при ожидании завершения группы связанных задач.The runtime also provides an exception-handling model that enables you to throw an exception from a task and handle that exception when you wait for the associated task group to finish. Дополнительные сведения об этой модели обработки исключений см. в разделе обработка исключений.For more information about this exception-handling model, see Exception Handling.

Сравнение task_group с structured_task_groupComparing task_group to structured_task_group

Несмотря на то что мы рекомендуем использовать task_group или parallel_invoke вместо класса structured_task_group, бывают ситуации, в которых вы захотите использовать класс structured_task_group, например при создании параллельного алгоритма, который выполняет переменное количество задач или которому требуется поддержка отмены.Although we recommend that you use task_group or parallel_invoke instead of the structured_task_group class, there are cases where you want to use structured_task_group, for example, when you write a parallel algorithm that performs a variable number of tasks or requires support for cancellation. В этом разделе описываются различия между классами task_group и structured_task_group.This section explains the differences between the task_group and structured_task_group classes.

Класс task_group является потокобезопасным.The task_group class is thread-safe. Поэтому можно добавлять задачи в объект task_group из нескольких потоков и ожидать или отменять объект task_group из нескольких потоков.Therefore you can add tasks to a task_group object from multiple threads and wait on or cancel a task_group object from multiple threads. Создание и уничтожение объекта structured_task_group должно происходить в одной лексической области.The construction and destruction of a structured_task_group object must occur in the same lexical scope. Кроме того, все операции с объектом structured_task_group должны происходить в одном потоке.In addition, all operations on a structured_task_group object must occur on the same thread. Исключением из этого правила являются методы Concurrency:: structured_task_group:: Cancel и concurrency:: structured_task_group:: is_canceling .The exception to this rule is the concurrency::structured_task_group::cancel and concurrency::structured_task_group::is_canceling methods. Дочерняя задача может вызывать эти методы для отмены родительской группы задач или проверки на предмет отмены в любое время.A child task can call these methods to cancel the parent task group or check for cancelation at any time.

Можно выполнять дополнительные задачи над task_group объектом после вызова метода Concurrency:: task_group:: wait или Concurrency:: task_group:: run_and_wait .You can run additional tasks on a task_group object after you call the concurrency::task_group::wait or concurrency::task_group::run_and_wait method. И наоборот, при выполнении дополнительных задач над structured_task_group объектом после вызова методов Concurrency:: structured_task_group:: wait или Concurrency:: structured_task_group:: run_and_wait поведение не определено.Conversely, if you run additional tasks on a structured_task_group object after you call the concurrency::structured_task_group::wait or concurrency::structured_task_group::run_and_wait methods, then the behavior is undefined.

Поскольку класс structured_task_group не синхронизируется в потоках, он имеет меньше затрат на выполнение, чем класс task_group.Because the structured_task_group class does not synchronize across threads, it has less execution overhead than the task_group class. Таким образом, если проблема не требует планирования работы в нескольких потоках и нельзя использовать алгоритм parallel_invoke, класс structured_task_group класс может помочь написать более производительный код.Therefore, if your problem does not require that you schedule work from multiple threads and you cannot use the parallel_invoke algorithm, the structured_task_group class can help you write better performing code.

При использовании одного объекта structured_task_group внутри другого объекта structured_task_group внутренний объект должен быть завершен и уничтожен до завершения внешнего объекта.If you use one structured_task_group object inside another structured_task_group object, the inner object must finish and be destroyed before the outer object finishes. Класс task_group не требуется для завершения вложенных групп задач до завершения внешней группы.The task_group class does not require for nested task groups to finish before the outer group finishes.

Неструктурированные группы задач и структурированные группы задач работают с дескрипторами задач по-разному.Unstructured task groups and structured task groups work with task handles in different ways. Вы можете передавать рабочие функции непосредственно в объект task_group; объект task_group будет создавать дескриптор задач и управлять им.You can pass work functions directly to a task_group object; the task_group object will create and manage the task handle for you. Класс structured_task_group требует, чтобы вы управляли объектом task_handle для каждой задачи.The structured_task_group class requires you to manage a task_handle object for each task. Каждый объект task_handle должен оставаться допустимым в течение всего времени существования связанного объекта structured_task_group.Every task_handle object must remain valid throughout the lifetime of its associated structured_task_group object. Используйте функцию Concurrency:: make_task , чтобы создать task_handle объект, как показано в следующем примере:Use the concurrency::make_task function to create a task_handle object, as shown in the following basic example:

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

Для управления дескрипторами задач в случаях, когда имеется переменное число задач, используйте подпрограммы выделения стека, например _malloca или класс контейнера, например std::vector.To manage task handles for cases where you have a variable number of tasks, use a stack-allocation routine such as _malloca or a container class, such as std::vector.

И task_group, и structured_task_group поддерживают отмену.Both task_group and structured_task_group support cancellation. Дополнительные сведения об отмене см. в разделе Отмена в библиотеке PPL.For more information about cancellation, see Cancellation in the PPL.

ПримерExample

В следующем базовом примере показано, как работать с группами задач.The following basic example shows how to work with task groups. В этом примере используется алгоритм parallel_invoke для выполнения двух задач одновременно.This example uses the parallel_invoke algorithm to perform two tasks concurrently. Каждая задача добавляет подзадачи в объект task_group.Each task adds sub-tasks to a task_group object. Обратите внимание, что класс task_group позволяет добавлять в него задачи одновременно нескольким задачам.Note that the task_group class allows for multiple tasks to add tasks to it concurrently.

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

Ниже приведен пример выходных данных для данного примера.The following is sample output for this example:

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

Поскольку алгоритм parallel_invoke выполняет задачи параллельно, порядок выходных сообщений может меняться.Because the parallel_invoke algorithm runs tasks concurrently, the order of the output messages could vary.

Полные примеры, демонстрирующие использование parallel_invoke алгоритма, см. в разделе как использовать parallel_invoke для написания параллельной подпрограммы сортировки и как использовать parallel_invoke для выполнения параллельных операций.For complete examples that show how to use the parallel_invoke algorithm, see How to: Use parallel_invoke to Write a Parallel Sort Routine and How to: Use parallel_invoke to Execute Parallel Operations. Полный пример использования task_group класса для реализации асинхронных фьючерсов см. в разделе Пошаговое руководство. Реализация фьючерсов.For a complete example that uses the task_group class to implement asynchronous futures, see Walkthrough: Implementing Futures.

Надежное программированиеRobust Programming

Убедитесь, что понимаете роль отмены и обработки исключений при использовании задач, групп задач и параллельных алгоритмов.Make sure that you understand the role of cancellation and exception handling when you use tasks, task groups, and parallel algorithms. Например, в дереве параллельной работы отмененная задача предотвращает запуск дочерних задач.For example, in a tree of parallel work, a task that is canceled prevents child tasks from running. Это может привести к проблемам, если одна из дочерних задач выполняет операцию, важную для приложения, например высвобождает ресурс.This can cause problems if one of the child tasks performs an operation that is important to your application, such as freeing a resource. Кроме того, если дочерняя задача создает исключение, это исключение может распространиться через деструктор объекта и вызвать неопределенное поведение в приложении.In addition, if a child task throws an exception, that exception could propagate through an object destructor and cause undefined behavior in your application. Пример, демонстрирующий эти моменты, см. в разделе понимание того, как отмена и обработка исключений влияет на уничтожение объектов в рекомендациях в документе Библиотека параллельных шаблонов.For an example that illustrates these points, see the Understand how Cancellation and Exception Handling Affect Object Destruction section in the Best Practices in the Parallel Patterns Library document. Дополнительные сведения о моделях отмены и обработки исключений в PPL см. в разделе об отмене и обработке исключений.For more information about the cancellation and exception-handling models in the PPL, see Cancellation and Exception Handling.

ЗаголовокTitle ОписаниеDescription
Как использовать parallel_invoke для написания параллельной подпрограммы сортировкиHow to: Use parallel_invoke to Write a Parallel Sort Routine Показывается, как использовать алгоритм parallel_invoke для повышения производительности алгоритма битонной сортировки.Shows how to use the parallel_invoke algorithm to improve the performance of the bitonic sort algorithm.
Инструкции. Использование parallel_invoke для выполнения параллельных операцийHow to: Use parallel_invoke to Execute Parallel Operations Показывается, как использовать алгоритм parallel_invoke для повышения производительности программы, выполняющей несколько операций с общим источником данных.Shows how to use the parallel_invoke algorithm to improve the performance of a program that performs multiple operations on a shared data source.
Как создать задачу, которая завершается после задержкиHow to: Create a Task that Completes After a Delay Показывает, как использовать task классы, cancellation_token_source , cancellation_token и task_completion_event для создания задачи, которая завершается после некоторой задержки.Shows how to use the task, cancellation_token_source, cancellation_token, and task_completion_event classes to create a task that completes after a delay.
Пошаговое руководство. Реализация фьючерсовWalkthrough: Implementing Futures Показано, как объединить существующие функциональные возможности в среде выполнения с параллелизмом в то, что делает больше.Shows how to combine existing functionality in the Concurrency Runtime into something that does more.
Библиотека параллельных шаблоновParallel Patterns Library (PPL) Описывается библиотека PPL, которая предоставляет императивную модель программирования для разработки параллельных приложений.Describes the PPL, which provides an imperative programming model for developing concurrent applications.

СсылкаReference

Класс Task (среда выполнения с параллелизмом)task Class (Concurrency Runtime)

Класс task_completion_eventtask_completion_event Class

Функция when_allwhen_all Function

Функция when_anywhen_any Function

Класс task_grouptask_group Class

Функция parallel_invokeparallel_invoke Function

Класс structured_task_groupstructured_task_group Class