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

В этом документе описана роль задач и групп задач в среде выполнения с параллелизмом.Задача — это единица работы, выполняющая конкретные действия.Задача обычно выполняется параллельно с другими задачами и может быть дополнительные, декомпозиции в тонкозернистый, задачи.Группа задач упорядочивает коллекцию задач.

Используйте задачи при написании асинхронного кода, определенную операцию произойти после завершения асинхронной операции.Например, можно использовать задачу в асинхронном режиме чтения из файла и задача продолжения, объяснена далее в этом документе, обработка данных после ее доступной.И наоборот, целевые группы для разложения параллельной работы на меньшие части.Предположим, например, что имеется рекурсивный алгоритм, разделяющий оставшуюся работу на два раздела.Можно использовать группы целевого назначения для выполнения этих секций одновременно, а затем ждете разделенный рабочего процесса.

СоветСовет

Если требуется применить одну и ту же процедуру к каждому элементу коллекции параллельно, использовать параллельный алгоритм, например concurrency::parallel_for вместо задачи или группы целевого назначения.Дополнительные сведения об алгоритмах параллельной обработки см. в разделе Параллельные алгоритмы.

Ключевых этапах

  • При передаче переменных лямбда-выражению по ссылке, необходимо следить, чтобы переменная существовала до завершения задачи.

  • Используйте задачи (класс concurrency::task ) при написании асинхронный код.

  • Используйте группы целевого назначения (например, класс concurrency::task_group или алгоритм concurrency::parallel_invoke ), когда требуется разложить параллельной работы на меньшие части и затем подождать, меньшие части.

  • Используйте метод concurrency::task::then для создания продолжения.Продолжение задачи, которая выполняется асинхронно после завершения другой задачи.Можно соединить любое количество продолжений для формирования цепочки асинхронной работы.

  • Задача-, основанное на продолжение всегда запланировано для выполнения задача отменена завершения предшествующей задачи, даже если задача предшествующей задачи или вызывает исключение.

  • Используйте concurrency::when_all для создания задачи, которая выполняется после того, как каждый элемент набора задач.Используйте concurrency::when_any для создания задачи, которая завершается после того как один элемент набора задач.

  • Задачи и группы целевого назначения могут участвовать в механизме отмены PPL.Дополнительные сведения см. в разделе Отмена в библиотеке параллельных шаблонов.

  • Чтобы получить сведения о том, как среда выполнения обрабатывает исключений, создаваемых задачами и группами целевого назначения см. в разделе Обработка исключений в среде выполнения с параллелизмом.

в этом документе

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

  • Класс задачи

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

  • Value-of, Основанный на Задача- на основе продолжений

  • Создание задачи

    • Функция when_all

    • When_any функция

  • Задержка выполнение задачи

  • Группы целевого назначения

  • Сравнение task_group и structured_task_group

  • Пример

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

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

Лямбда-выражения общий способ определения рабочего процесса, который выполняется задачами и группами целевого назначения из-за их succinct синтаксиса.Ниже приведены некоторые советы по использованию:

  • Поскольку задачи, обычно выполняемые в фоновых потоках, помните о времени существования объектов при перенаправите переменные в лямбда-выражениях.При перенаправите переменная значением копию этой переменной выполняется в теле лямбда-выражения.При перенаправите по ссылке, копия не выполняется.Поэтому убедитесь в том, что время существования любой переменной, перенаправите ссылкой переживает задачу, которая использования.

  • Как правило, не собирает переменные, выберите в стеке.Это также означает, что не следует перенаправить переменные-члены объектов, которые выделены в стеке.

  • Точн о переменных для перенаправите в лямбда-выражениях, помогающие указать, что перенаправите значение для ссылки.Поэтому не рекомендуется использовать параметры [=] или [&] для лямбда-выражений.

Общий шаблон, когда одна задача в присвоитях цепочки продолжения к переменной, а другая задача считывают ее.Невозможно перенаправить значением поскольку каждая задача продолжения держала бы свою копию этой переменной.Для стек- выбранных переменных, также нельзя зафиксировать по ссылке, так как переменная больше не сможет быть допустимой.

Решить эту проблему, использовать интеллектуальное указатель, как std::shared_ptr, чтобы создать программу-оболочку переменную и передать указатель значение автоматически.Это основной объект может быть присвоено и чтения и переживет задачи, которые его используют.Используйте этот метод, даже если переменная указателя или ссылка- подсчитанный дескриптор (^) к объекту среды выполнения Windows.Ниже приведен простой пример:

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

Дополнительные сведения о лямбда-выражениях см. в разделе Lambda expressions in C++.

[Верх]

Класс задачи

Можно использовать класс concurrency::task для создания задачи в набор независимых операций.Эта модель составления поддерживается придумкой продолжений.Продолжение включает код, который будет выполняться, когда предыдущая или предшествующей задачи, задача.Результат задачи предшествующей задачи передаваться в качестве входных данных один или более задач продолжения.Задача предшествующей задачи продолжения завершения всех задач, ожидающих на нем запланированы для выполнения.Каждая задача продолжения возвращает копию результата задачи предшествующей задачи.В свою очередь эти задачи продолжения также могут быть задачи предшествующей задачи для других продолжений, таким образом создается цепь задач.Продолжения помогут создать произвольн- длины цепочки задач, которые имеют особые зависимости между ними.Кроме того, задача может участвовать в отмене или до начала выполнения задач или в кооперативном образом, пока он работает.Дополнительные сведения об этой модели отмены см. в разделе Отмена в библиотеке параллельных шаблонов.

task класс шаблона.Параметр типа T тип результата, созданного задачей.Этот тип может быть void если задача не возвращает значение.T не может использовать модификатор const.

При создании задачи можно задать рабочую функцию, которая выполняет тело задачи.Эта рабочая функция расположена в форме лямбда-функции, указателя функции или функции.Для ожидания завершения задачи без получения результата, вызовите метод concurrency::task::wait.Метод task::wait возвращает значение concurrency::task_status, описывает ли задача была завершена или отменена.Для получения результата задачи, вызовите метод concurrency::task::get.Этот метод вызывает task::wait для ожидания завершения задачи, и, следовательно, блокируют выполнение текущего потока до тех пор, пока результат не будет доступен.

В следующем примере показано, как создать задачу, дождаться его результат, и отобразить ее значение.В примерах, приведенных в этой документации, используются лямбда-функции, поскольку они обеспечивают более succinct синтаксис.Однако можно также использовать указатели функций и объекты функции при использовании задачи.

// 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 вместо объявления типа.Например, рассмотрим следующий код, который создает и отображает матрицу идентификатора.

// 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 для создания эквивалентная операция.

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 или в соответствии задача- продолжению.Дополнительные сведения о механизме обработки ошибок задач см. в разделе Обработка исключений в среде выполнения с параллелизмом.

Пример, использующий task, concurrency::task_completion_event, отмена см. в разделе Пошаговое руководство. Подключение с использованием задач и HTTP-запроса XML (IXHR2).(Класс task_completion_event описывается далее в этом документе).

СоветСовет

Чтобы получить сведения, относящиеся к задачам в приложениях Магазина Windows см. в разделе Asynchronous programming in C++ и Создание асинхронных операций в C++ для приложений для Магазина Windows.

[Верх]

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

В асинхронном программировании очень часто одна асинхронная операция по завершении вызывает вторую операцию и передает в нее данные.Обычно это делается с помощью обратных вызовов.Среду выполнения с параллелизмом, одну и ту же функциональность обеспечивается задач продолжения.Задача продолжения (также известная как продолжение) — это асинхронная задача, вызываемая другой предшествующей задачей, при ее завершении.С помощью продолжений можно:

  • Передайте данные из предшествующей задачи в продолжение.

  • Укажите точные условия, при которых продолжение вызван метод или не вызывается.

  • Отменить продолжение или перед тем, как начать или совместно во время его выполнения.

  • Задайте подсказки о том, как продолжение должно быть запланировано.(Это применяется только к Магазина Windows приложения.Дополнительные сведения см. в разделе Создание асинхронных операций в C++ для приложений для Магазина Windows.)

  • Вызовите несколько продолжений из одного и того же предшествующей задачи.

  • Вызовите одно продолжение, когда все или любой из нескольких antecedents полных.

  • Прикуйте продолжения один за другим на любой длины.

  • Используйте продолжение для обработки исключений, которые вызываются антецедентом.

Эти функции позволяют выполнять одну или несколько задач, когда первая задача завершается.Например, можно создать продолжение, сжимает файл после первой задачи считывает ее с диска.

В следующем примере изменяется и предыдущее для использования метода concurrency::task::then планировании продолжения, введите значение задачи предшествующей задачи, если она доступна.

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

Можно вкладывать задачи и привязан к длине.Задача также может иметь несколько продолжений.Следующий пример иллюстрирует базовую цепочку продолжения, которая увеличивает значение 3 времени предыдущей задачи.

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

Продолжение может также возвращать другую задачу.Если отмена, то эта задача исполнена последующим перед продолжением.Этот метод как асинхронный развертывания.Асинхронный при развертывании полезен, когда требуется для выполнения дополнительных действий в фоновом режиме, но не хотите текущую задачу отключить текущий поток.(Это общее в приложениях Магазина Windows, где продолжения могут выполняться в потоке пользовательского интерфейса).В следующем примере показывает 3 задачи.Первая задача вернет другую задачу, которая выполнена, прежде чем задача продолжения.

// 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
*/
Важное примечаниеВажно

Если продолжение задачи возвращает вложенные задачи типа N итоговый задача имеет тип N, не task<N> и завершается при вложенная задача завершается.Иначе говоря, продолжение выполняет развертывание вложенные задачи.

[Верх]

Value-of, Основанный на Задача- на основе продолжений

Заданный объект task возвращаемый тип которого T, можно реализовать значение типа T или task<T> его задач продолжения.Продолжение который принимает тип T как value-of на основе продолжения.Value-of, основанное на продолжение запланировано для выполнения, когда задача завершается без ошибок и предшествующей задачи не отменено.Продолжение который принимает тип task<T> в качестве своего параметра, как задача-, основанное на продолжение.Задача-, основанное на продолжение всегда запланировано для выполнения задача отменена завершения предшествующей задачи, даже если задача предшествующей задачи или вызывает исключение.Затем можно вызвать task::get для получения результата задачи предшествующей задачи.Если задача была отменена предшествующей задачи, task::get вызывает concurrency::task_canceled.Если задача предшествующей задачи возникло исключение, то rethrows task::get, исключение.Задача-, основанное на продолжение не помечены как отменено, если его задача предшествующей задачи отменена.

[Верх]

Создание задачи

Этот раздел описывает функции concurrency::when_all и concurrency::when_any, которые помогут составлять множество задач реализации общих шаблонов.

Dd492427.collapse_all(ru-ru,VS.110).gifФункция when_all

Функция when_all создает задачу, которая завершается после полного набора задач.Эта функция возвращает объект std::vector, который содержит результат выполнения каждой задачи в наборе.Следующий простой пример использует when_all для создания задачи, которая представляет завершение 3 других задач.

// 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.
*/
ПримечаниеПримечание

Задачи, передаваемые в when_all должны быть равномерны.Иначе говоря, все они возвращают один и тот же тип.

Можно также использовать синтаксис && для создания задачи, которая завершается после полного набора задач, как показано в следующем примере.

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

Обычно для использования продолжения вместе с when_all для выполнения действий после завершения набор задач.В следующем примере изменяется и предыдущее, чтобы напечатать сумму 3 задач, каждый из которых предоставляет int.

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

В этом примере можно также указать task<vector<int>> для создания задача-, основанное на продолжение.

Предупреждающее замечаниеВнимание

Если отменена или вызывает любая задача в набор задач исключение, when_all немедленно завершает и не ожидает остальные задачи.Если исключение возникает, rethrows времени выполнения исключение при вызове task::get или task::wait на объекте, который when_all возвращает task.Если штрихи более чем одной задачи, среда выполнения выбрать один из них.Поэтому, если она возникает исключение, то убедитесь в том, что ожидается все задачи завершиться.

[Верх]

Dd492427.collapse_all(ru-ru,VS.110).gifWhen_any функция

Функция when_any создает задачу, которая завершается, когда первая задача в набор задач.Эта функция возвращает объект std::pair, содержащий результат завершенной задачи и индексов этой задачи в наборе.

when_any функция особенно полезна в следующих сценариях:

  • Операции резервного копирования.Рассмотрим алгоритм или операцию, который может выполняться несколькими способами.Можно использовать функцию when_any для выбора завершения выполнения операции которой в первую очередь, а затем отменять остальные операции.

  • Чередуются операции.Можно запускать несколько операций, которые должны завершить все и использовать функцию when_any для обработки результатов по мере приближения завершения операции.После того как одна операция завершается, можно запустить один или несколько дополнительных задач.

  • Дросселированные операции.Можно использовать функцию when_any для расширения предыдущий сценарий, чтобы ограничить количество одновременных операций.

  • Операции с истекшим сроком действия.Можно использовать функцию when_any для выбора между одним или несколькими задачами и задачей, после завершения определенного времени.

Как и в случае с when_all она применяется для продолжения, имеет when_any выполнить действие, когда первые в наборе завершения задачи.Следующий простой пример использует when_any для создания задачи, которая завершается, когда первая из 3 других задач.

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

В этом примере можно также указать task<pair<int, size_t>> для создания задача-, основанное на продолжение.

ПримечаниеПримечание

Как и в случае с when_all, задачи, передаваемая методу все when_any возвращают один и тот же тип.

Можно также использовать синтаксис || для создания задачи, которая завершается после первой задачи в набор задач, как показано в следующем примере.

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

[Верх]

Задержка выполнение задачи

Иногда требуется задержать выполнение задач до тех пор, пока условие не удовлетворяется или запускать задачу в ответ на внешнее событие.Например, в асинхронном программировании, можно запустить задачу в ответ на событие завершения ВВОДА-ВЫВОДА.

2 Способа запуска этого использовать продолжение или запускать задачу и ожидание событии в рабочей функции задачи.Однако случаи, когда он не возможное для использования одного из этих методов.Например, чтобы создать продолжение, необходимо иметь задачу предшествующей задачи.Однако если отсутствуют задачи предшествующей задачи, можно создать событие завершения задачи и более позднюю цепь, событие завершения предшествующей задачи в задаче, когда он станет доступным.Кроме того, поскольку ожидание задача также блокирует поток, можно использовать события завершения задачи выполнение работы после завершения асинхронной операции, и, следовательно, освобождаете поток.

Справки класса concurrency::task_completion_event упрощают ту композицию задач.Например, класс task параметр типа T тип результата, созданного задачей.Этот тип может быть void если задача не возвращает значение.T не может использовать модификатор const.Обычно объект task_completion_event реализуется поток или задачи, просигнализируют его, когда значение для него станет доступным.Одновременно одну или несколько задач установлены как прослушиватели события.Если событие установлено, прослушиватель задает действие полного и их продолжения запланированы для выполнения.

Пример, использующий task_completion_event для реализации задачи, которая завершается после задержки, см. в разделе Практическое руководство. Создание задачи, выполняемой после задержки.

[Верх]

Группы целевого назначения

Группа задач упорядочивает коллекцию задач.Группы задач помещают задачи в очередь переноса нагрузки.Планировщик удаляет задачи из этой очереди и выполняет их с использованием доступных вычислительных ресурсов.После добавления задач в группу можно ожидать выполнения всех задач или отменять не начатые задачи.

PPL использует классы concurrency::task_group и concurrency::structured_task_group для представления групп целевого назначения, а класс concurrency::task_handle для представления задач, выполняемых в этих группах.Класс task_handle содержит код, выполняющий работу.Например, класс task, рабочая функция расположена в форме лямбда-функции, действующий указатель или объект функции.Непосредственно с объектами task_handle обычно работать не нужно.Вместо этого можно передавать рабочие функции группе задач, которая создает объекты task_handle и управляет ими.

PPL разделяет группы задач на две категории: неструктурированные группы задач и структурированные группы задач.PPL использует класс task_group для представления неструктурированные группы целевого назначения и класс structured_task_group для представления структурированные группы целевого назначения.

Важное примечаниеВажно

PPL также определяет алгоритм concurrency::parallel_invoke, который использует класс structured_task_group для выполнения набора задач в параллельном режиме.Так как у алгоритма parallel_invoke более сжатый синтаксис, рекомендуется при возможности использовать его, а не класс structured_task_group.Алгоритм parallel_invoke более подробно описан в разделе Параллельные алгоритмы.

Алгоритм parallel_invoke используется при наличии нескольких независимых задач, которые нужно выполнять одновременно, а перед продолжением необходимо дождаться завершения всех задач.Этот метод часто называют параллелизм вилок и соединения.Алгоритм task_group используется при наличии нескольких независимых задач, которые нужно выполнять одновременно, но дожидаться завершения задач необходимо позднее.Например, можно добавить задачи в объект task_group и дождаться завершения задач в другой функции или потоке.

Группы задач поддерживают принцип отмены.Отмена позволяет сообщить всем активным задачам, что необходимо отменить общую операцию.Отмена также предотвращает запуск еще не начатых задач.Дополнительные сведения об отмене см. в разделе Отмена в библиотеке параллельных шаблонов.

Среда выполнения также предоставляет модель обработки исключений, которая позволяет создать исключение из задачи и обработать его, если необходимо завершить связанную группу задач.Дополнительные сведения о модели обработки исключений см. в разделе Обработка исключений в среде выполнения с параллелизмом.

[Верх]

Сравнение task_group и structured_task_group

Хотя рекомендуется использовать task_group или parallel_invoke вместо класса structured_task_group, случаи, когда необходимо использовать structured_task_group, например при написании параллельный алгоритм, который выполняет переменное число задач или необходима поддержка отмены.В этом разделе описаны различия между классами task_group и structured_task_group.

Класс task_group является потокобезопасным.Поэтому можно добавить задачу к объекту task_group из нескольких потоков и дождаться on или отменить объект task_group из нескольких потоков.Конструирование и деструкция объекта structured_task_group должны происходить в одной лексической области.Кроме того, все операции с объектом structured_task_group должны происходить в одном потоке.Исключением из этого правила являются методы concurrency::structured_task_group::cancel и concurrency::structured_task_group::is_canceling.Дочерняя задача может вызывать эти методы для отмены родительской группы задач или проверки отмены в любой момент времени.

Можно выполнить дополнительные задачи в объекте task_group после вызова метода concurrency::task_group::wait или concurrency::task_group::run_and_wait.Наоборот, нельзя выполнить дополнительные задачи в объекте structured_task_group после вызова методов concurrency::structured_task_group::wait или concurrency::structured_task_group::run_and_wait.

Так как класс structured_task_group не синхронизируется по потокам, дополнительная нагрузка при его выполнении меньше, чем для класса task_group.Следовательно, если для решения задачи не требуется планировать работу в нескольких потоках и нельзя использовать алгоритм parallel_invoke, класс structured_task_group может помочь написать более производительный код.

При использовании одного объекта structured_task_group внутри другого объекта structured_task_group внутренний объект должен быть завершен и уничтожен до завершения внешнего объекта.Класс task_group не требует, чтобы вложенные группы задач завершались до завершения внешних задач.

Неструктурированные и структурированные группы задач работают с дескрипторами задач по-разному.Можно передавать рабочие функции непосредственно объекту task_group; объект task_group сам создаст дескриптор задач и будет им управлять.Для класса structured_task_group необходимо управлять объектом task_handle для каждой задачи.Все объекты task_handle должны быть допустимы на протяжении всего времени существования связанного объекта structured_task_group.Используйте функцию concurrency::make_task для создания объекта task_handle, как показано в следующем основном примере:

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

Чтобы управлять дескрипторами задач в случае переменного количества задач, следует использовать процедуру выделения памяти стека, например _malloca, или класс контейнера, например std::vector.

Классы task_group и structured_task_group поддерживают отмену.Дополнительные сведения об отмене см. в разделе Отмена в библиотеке параллельных шаблонов.

[Верх]

Пример

В следующем общем примере показано, как работать с группами задач.В этом примере алгоритм parallel_invoke используется для параллельного выполнения двух задач.Каждая задача добавляет подзадачи в объект task_group.Обратите внимание, что класс task_group позволяет добавлять к себе несколько задач параллельно.

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

Ниже приведен пример выходных данных для данного примера.

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

Поскольку алгоритм parallel_invoke выполняет задачи параллельно, порядок выходных сообщений может меняться.

Полные примеры, демонстрирующие использование алгоритма parallel_invoke, см. в разделах Практическое руководство. Использование функции parallel_invoke для написания программы параллельной сортировки и Практическое руководство. Использование функции parallel_invoke для выполнения параллельных операций.Полный пример использования класса Walkthrough: Implementing Futures для реализации асинхронных фьючерсов см. в разделе Пошаговое руководство. Реализация фьючерсов.

[Верх]

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

Убедитесь, что понимают роль отмены и обработка ошибок при использовании задачи, группы целевого назначения и параллельные алгоритмы.Например, в дереве параллельной работы отмененная задача не позволяет выполняться дочерним задачам.Это может привести к проблемам, если одна из дочерних задач выполняет операцию, важную для приложения, например высвобождает ресурс.Кроме того, если дочерняя задача создает исключение, оно может распространиться через деструктор объекта и вызвать неопределенное поведение приложения.Пример, иллюстрирующий эти случаи, см. в разделе Understand how Cancellation and Exception Handling Affect Object Destruction документа "Рекомендации по использованию библиотеки параллельных шаблонов".Дополнительные сведения о моделях отмены и обработки исключений в PPL см. в разделах Отмена в библиотеке параллельных шаблонов и Обработка исключений в среде выполнения с параллелизмом.

[Верх]

Связанные разделы

Заголовок

Описание

Практическое руководство. Использование функции parallel_invoke для написания программы параллельной сортировки

Показывает использование алгоритма parallel_invoke для повышения производительности алгоритма битонной сортировки.

Практическое руководство. Использование функции parallel_invoke для выполнения параллельных операций

Показывает использование алгоритма parallel_invoke для улучшения производительности программы, выполняющей несколько операций с общим источником данных.

Практическое руководство. Создание задачи, выполняемой после задержки

Показывает, как использовать task, cancellation_token_source, cancellation_token и классы task_completion_event для создания задачи, которая выполняется после задержки.

Пошаговое руководство. Реализация фьючерсов

Показывает объединение существующих функциональных возможностей в среде выполнения с параллелизмом для создания дополнительных функциональных возможностей.

Библиотека параллельных шаблонов

Описывает библиотеку PPL, которая предоставляет императивную модель программирования для разработки параллельных приложений.

Ссылки

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

Класс task_completion_event

Функция when_all

Функция when_any

Класс task_group

Функция parallel_invoke

Класс structured_task_group