Compartir a través de


Paralelismo de tareas (Runtime de simultaneidad)

En este documento se describe el rol de las tareas y los grupos de tareas en el runtime de simultaneidad.Una tarea es una unidad de trabajo que realiza un trabajo concreto.Una tarea se ejecuta normalmente en paralelo con otras tareas y se puede descomponer en otros, más concreto, tareas.Un grupo de tareas organiza una colección de tareas.

Use las tareas cuando escriba asincrónico codificado y desee alguna operación para después de que la operación asincrónica finaliza.Por ejemplo, puede utilizar una tarea asincrónica de leer de un archivo y una tarea de continuación, que se explica más adelante en este documento, de procesar los datos después de que esté disponible.A la inversa, grupos de tareas de uso para descomponer el trabajo paralelo en fragmentos pequeños.Supongamos, por ejemplo, que tiene un algoritmo recursivo que divide el trabajo restante en dos particiones.Puede utilizar grupos de tareas para ejecutar estas particiones simultáneamente, y después espera el trabajo dividido para completar.

SugerenciaSugerencia

Si desea aplicar la misma rutina a cada elemento de una colección en paralelo, utilice un algoritmo paralelo, como concurrency::parallel_for, en lugar de una tarea o un grupo de tareas.Para obtener más información acerca de los algoritmos paralelos, vea Algoritmos paralelos.

Puntos clave

  • Al pasar las variables a una expresión lambda por referencia, se debe garantizar que la duración de esa variable persista hasta que finalice la tarea.

  • Use las tareas (la clase de concurrency::task ) cuando se escribe código asincrónico.

  • Use grupos de tareas (como la clase de concurrency::task_group o el algoritmo de concurrency::parallel_invoke ) cuando necesite descomponer el trabajo paralelo en fragmentos pequeños y después esperar esos fragmentos pequeños para completar.

  • Utilice el método de concurrency::task::then para crear continuaciones.Una continuación es una tarea que se ejecuta de forma asincrónica después de otra tarea se completa.Puede conectar cualquier número de continuaciones para formar una cadena de trabajo asincrónico.

  • Una continuación tarea- basada se programa siempre para la ejecución cuando los finals en la tarea anterior, incluso cuando la tarea antecedente se cancela o produce una excepción.

  • Utilice concurrency::when_all para crear una tarea completar después de cada miembro de un conjunto de tareas se complete.Utilice concurrency::when_any para crear una tarea completar después de que un miembro de un conjunto de tareas se complete.

  • Las tareas y los grupos de tareas pueden participar en el mecanismo de cancelación de PPL.Para obtener más información, vea Cancelación en la biblioteca PPL.

  • Para obtener información sobre cómo el runtime controla las excepciones producidas por las tareas y grupos de tareas, vea Control de excepciones en el runtime de simultaneidad.

En este documento

  • Utilizar expresiones lambda

  • La clase task

  • Tareas de continuación

  • Siguiente valor Basado en continuaciones Tarea- Basadas

  • Tareas que componen

    • La función de when_all

    • La función when_any

  • Ejecución y de la tarea

  • Grupos de tareas

  • Comparación entre task_group y structured_task_group

  • Ejemplo

  • Programación sólida

Utilizar expresiones lambda

Las expresiones lambda son una manera común de definir el trabajo que realiza tareas y grupos de tareas debido a su sintaxis concisa.A continuación se muestran algunas sugerencias en utilizarlas:

  • Dado que las tareas suelen en subprocesos de fondo, tenga presente la duración del objeto en que se captura variables en expresiones lambda.Cuando se captura una variable por valor, una copia de esa variable se realiza en el cuerpo de la expresión lambda.Cuando se captura por referencia, una copia no se realiza.Por consiguiente, asegúrese de que la duración de una variable que se captura por referencia sobrevive a la tarea que las aplicaciones él.

  • No captura normalmente las variables por que se asignan en la pila.Esto también significa que no se debe capturar a variables miembro de los objetos que se asignan en la pila.

  • Es explícito sobre las variables que se captura en expresiones lambda para ayudarle a identificar qué está capturando por valor o por referencia.Por este motivo no se recomienda utilizar las opciones de [=] o de [&] para las expresiones lambda.

Un modelo común es cuando una tarea en asignaciones de una cadena de continuación a una variable, y otras lecturas de la tarea que variable.No puede capturar por valor porque cada tarea de continuación llevaría a cabo otra copia de esa variable.Para las variables pila- asignadas, no puede capturar por referencia porque la variable haya dejado de ser válida.

Para resolver este problema, utilice un puntero inteligente, como std::shared_ptr, para ajustar la variable y pasar el puntero inteligente por valor.Al hacerlo, el objeto subyacente se puede asignar a y leer y sobrevivirá a las tareas que la utilizan.Utilice esta técnica incluso si la variable es un puntero o un identificador referencia- contado (^) a un objeto de Windows en tiempo de ejecución.A continuación se muestra un ejemplo básico:

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

Para obtener más información sobre las expresiones lambda, vea Expresiones lambda en C++.

[parte superior]

La clase task

Puede utilizar la clase de concurrency::task para crear tareas en un conjunto de operaciones dependientes.Este modelo de composición admite la noción de continuaciones.Una continuación habilita el código que se ejecutará al anterior, o el antecedente, tarea completa.El resultado de la tarea antecedente se pasa como entrada para las una o más tareas de continuación.Cuando una tarea anterior se complete, cualquier tarea de continuación que está esperando se programa para la ejecución.Cada tarea de continuación recibe una copia del resultado de la tarea antecedente.A su vez, esas tareas de continuación también pueden ser tareas antecedentes para otras continuaciones, con lo que se crea una cadena de tareas.Las continuaciones le ayudan a crear cadenas de la arbitrario- longitud de las tareas que tienen dependencias concretas entre ellas.Además, una tarea puede participar en la cancelación o antes de que se iniciaran las tareas o de manera cooperativa mientras se ejecuta.Para obtener más información sobre este modelo de cancelación, vea Cancelación en la biblioteca PPL.

task es una clase de plantilla.El parámetro de tipo T es el tipo del resultado generado por la tarea.Este tipo puede ser void si la tarea no devuelve un valor.T no puede utilizar el modificador de const .

Cuando se crea una tarea, se proporciona una función de trabajo que realiza el cuerpo de la tarea.Esta función de trabajo tiene forma de una función lambda, un puntero a función, o un objeto de función.Para esperar a que una tarea finalice sin la obtención de resultados, llame al método de concurrency::task::wait .El método de task::wait devuelve un valor de concurrency::task_status que describe si la tarea se completa o cancelado.Para obtener el resultado de la tarea, llame al método de concurrency::task::get .Este método llama a task::wait para esperar a que la tarea finalice, y por consiguiente bloquean la ejecución del subproceso actual hasta que el resultado esté disponible.

El ejemplo siguiente se muestra cómo crear una tarea, para esperar el resultado, y mostrar su valor.Los ejemplos de esta lambda de uso de la documentación funcionan porque proporcionan una sintaxis más concisa.Sin embargo, también puede utilizar punteros a función y objetos de función cuando se usa tareas.

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

La función de concurrency::create_task permite utilizar la palabra clave de auto en lugar de declarar el tipo.Por ejemplo, considere el siguiente código que crea y imprime la matriz de identidad:

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

Puede utilizar la función de create_task para que la operación equivalente.

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

Si se produce una excepción durante la ejecución de una tarea, los calcular las referencias de runtime esa excepción en la llamada subsiguiente a task::get o a task::wait, o a una continuación tarea- basada en.Para obtener más información sobre el mecanismo de control de excepciones de tarea, vea Control de excepciones en el runtime de simultaneidad.

Para obtener un ejemplo que utiliza task, concurrency::task_completion_event, la cancelación, vea Tutorial: Conectar usando tareas y solicitud HTTP XML (IXHR2).(Clase de El task_completion_event se describe más adelante en este documento).

SugerenciaSugerencia

Para obtener detalles específicos de las tareas en las aplicaciones de Tienda Windows , vea Asynchronous programming in C++ y Crear operaciones asincrónicas en C++ para aplicaciones de la Tienda Windows.

[parte superior]

Tareas de continuación

En la programación asincrónica, es muy común que una operación asincrónica, cuando se completa, invoque una segunda operación y le pase datos.Tradicionalmente, esto se hace utilizando métodos de devolución de llamada.En el runtime de simultaneidad, proporcionan la misma funcionalidad las tareas de continuación.Una tarea de continuación (o, simplemente, una continuación) es una tarea asincrónica invocada por otra tarea, que se denomina antecedente, cuando se completa el antecedente.Mediante las continuaciones, puede:

  • Pasar datos del antecedente a la continuación.

  • Especificar las condiciones precisas en las que se invoca o no se invoca la continuación.

  • Cancelar una continuación antes de iniciarse o cede mientras se ejecuta.

  • Proporciona sugerencias sobre cómo se debería programar la continuación.(Esto se aplica a las aplicaciones de Tienda Windows únicamente.Para obtener más información, vea Crear operaciones asincrónicas en C++ para aplicaciones de la Tienda Windows.)

  • Invocar varias continuaciones desde el mismo antecedente.

  • Invocar una continuación cuando o la totalidad de los antecedentes.

  • Encadenar las continuaciones uno tras otro hasta cualquier longitud.

  • Usar una continuación para controlar las excepciones producidas por el antecedente.

Estas características permiten ejecutar una o más tareas cuando la primera tarea completa.Por ejemplo, puede crear una continuación que comprimir un archivo después de la primera tarea lo lea desde el disco.

El ejemplo siguiente se modifica el anterior para utilizar el método de concurrency::task::then para programar una continuación que imprime el valor de la tarea antecedente cuando está disponible.

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

Puede encadenar y anidar tareas a cualquier longitud.Una tarea puede tener varias continuaciones.El ejemplo siguiente se muestra una cadena básica de continuación que aumente el valor de la tarea anterior tres veces.

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

Una continuación también puede devolver otra tarea.Si no se ha cancelado, esta tarea se ejecuta antes de continuación subsiguiente.Esta técnica se denomina desempaque asincrónico.El asincrónico que desempaqueta es útil cuando desee realizar el trabajo adicional en segundo plano, pero no desea que la tarea actual para bloquear el subproceso actual.(Esto es común en las aplicaciones de Tienda Windows , donde las continuaciones pueden ejecutarse en el subproceso de la interfaz de usuario).El ejemplo siguiente se muestran tres tareas.La primera tarea devuelve otra tarea se ejecute antes de que una tarea de continuación.

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

Cuando una continuación de una tarea devuelve una tarea anidada de Nescrita, la tarea resultante tiene el tipo N, no task<N>, y completa cuando la tarea anidada completa.Es decir la continuación realiza el desempaque de la tarea anidada.

[parte superior]

Siguiente valor Basado en continuaciones Tarea- Basadas

Dado un objeto de task cuyo tipo de valor devuelto sea T, puede proporcionar un valor de T cualquiera de task<T> sus tareas de continuación.Una continuación que toma el tipo T se denomina continuación siguiente valor basadaen.Una continuación siguiente valor basada se programa para la ejecución cuando la tarea anterior se complete sin errores y no se cancela.Una continuación que toma el tipo task<T> mientras el parámetro se denomina continuación tarea- basadaen.Una continuación tarea- basada se programa siempre para la ejecución cuando los finals en la tarea anterior, incluso cuando la tarea antecedente se cancela o produce una excepción.Puede llamar a task::get para obtener el resultado de la tarea antecedente.Si la tarea antecedente se cancela, task::get produce concurrency::task_canceled.Si la tarea antecedente produjo una excepción, los vuelve de task::get esa excepción.Una continuación tarea- basada no se marca como cancelada cuando su tarea antecedente se cancela.

[parte superior]

Tareas que componen

Esta sección describe concurrency::when_all y concurrency::when_any funciona, que pueden ayudarle a crear varias tareas para implementar modelos comunes.

Dd492427.collapse_all(es-es,VS.110).gifLa función de when_all

La función de when_all genera una tarea completar después de que un conjunto de tareas se complete.Esta función devuelve un objeto de std::vector que contiene el resultado de cada tarea en el conjunto.El siguiente ejemplo básico se utiliza when_all para crear una tarea que representa la finalización de tres otras tareas.

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

[!NOTA]

Las tareas que se pasa a when_all deben ser uniformes.Es decir deben todo el devuelven el mismo tipo.

También puede utilizar la sintaxis de && para generar una tarea completar después de que un conjunto de tareas completadas, como se muestra en el ejemplo siguiente.

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

Es común usar una continuación así como when_all para realizar la acción después de que un conjunto de final de las tareas.El ejemplo siguiente se modifica el anterior para imprimir la suma de tres tareas que cada genera un resultado de 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.
*/

En este ejemplo, también puede especificar task<vector<int>> para generar una continuación tarea- basada en.

Nota de precauciónPrecaución

Si cualquier tarea en un conjunto de tareas está cancelado o produce una excepción, when_all inmediatamente completa y espera las tareas restantes finalice.Si se produce una excepción, los vuelve de runtime la excepción al llamar a task::get o task::wait en el objeto de task que when_all devuelve.Si los tiros de más de una tarea, el runtime elija uno de ellos.Por consiguiente, si uno produce una excepción, asegúrese de que espere que todas las tareas en completarse.

[parte superior]

Dd492427.collapse_all(es-es,VS.110).gifLa función when_any

La función de when_any genera una tarea completar cuando la primera tarea en un conjunto de tareas completa.Esta función devuelve un objeto de std::pair que contiene el resultado de la tarea completada y de índice de esa tarea en el conjunto.

La función de when_any es especialmente útil en los escenarios siguientes:

  • Operaciones redundantes.Considere un algoritmo o una operación que se pueden realizar en gran medida.Puede utilizar la función de when_any para seleccionar la operación que finaliza primero y después cancelar operaciones restantes.

  • Operaciones intercaladas.Puede iniciar varias operaciones que todo debe finalizar y utilizar la función de when_any para procesar resultados como finals de cada operación.Después de que los finals de una operación, se pueden iniciar una o más tareas adicionales.

  • Restringir las operaciones.Puede utilizar la función de when_any para extender el escenario anterior limitando el número de operaciones simultáneas.

  • Operaciones expirado.Puede utilizar la función de when_any para seleccionar entre una o más tareas y una tarea que termina después de un tiempo concreto.

Como con when_all, lo habitual es utilizar una continuación que tiene when_any para realizar una acción cuando el primer en un conjunto de final de las tareas.El siguiente ejemplo básico se utiliza when_any para crear una tarea completar cuando el primer de otras tres tareas completa.

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

En este ejemplo, también puede especificar task<pair<int, size_t>> para generar una continuación tarea- basada en.

[!NOTA]

Como con when_all, las tareas que se pasa a when_any deben todo el devuelven el mismo tipo.

También puede utilizar la sintaxis de || para generar una tarea completar después de la primera tarea en un conjunto de tareas se complete, como se muestra en el ejemplo siguiente.

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

[parte superior]

Ejecución y de la tarea

A veces es necesario retrasar la ejecución de una tarea hasta que se cumple una condición, o iniciar una tarea en respuesta a un evento externo.Por ejemplo, en la programación asincrónica, puede que tenga que iniciar una tarea en respuesta a un evento de finalización de E/S.

Dos maneras de lograrlo es usar una continuación o iniciar una tarea y una espera en un evento de la función de trabajo de tarea.Sin embargo, hay casos donde no es posible utilizar una de estas técnicas.Por ejemplo, para crear una continuación, debe tener la tarea antecedente.Sin embargo, si no tiene la tarea anterior, puede crear un evento de finalización de la tarea y una cadena posterior que evento de finalización a la tarea antecedente cuando está disponible.Además, como una tarea en espera también bloquea un subproceso, puede utilizar eventos de finalización de la tarea para realizar el trabajo cuando una operación asincrónica y, por consiguiente libera un subproceso.

Ayuda de la clase de concurrency::task_completion_event simplifican tal composición de tareas.Como la clase de task , el parámetro de tipo T es el tipo del resultado generado por la tarea.Este tipo puede ser void si la tarea no devuelve un valor.T no puede utilizar el modificador de const .Normalmente, un objeto de task_completion_event se proporciona un subproceso o tarea que lo apuntan al valor para que esté disponible.Al mismo tiempo, una o más tareas se establecen como agentes de escucha de ese evento.Cuando se establece el evento, las tareas de agente de escucha completadas y sus continuaciones se han programado para ejecutarse.

Para obtener un ejemplo que utiliza task_completion_event para implementar una tarea completar después de un intervalo, considere Cómo: Crear una tarea que se complete después de un retardo.

[parte superior]

Grupos de tareas

Un grupo de tareas organiza una colección de tareas.Los grupos de tareas insertan las tareas en una cola de robo de trabajo.El programador quita las tareas de esta cola y las ejecuta con los recursos informáticos disponibles.Después de agregar tareas a un grupo de tareas, puede esperar a que finalicen todas las tareas o cancelar tareas que aún no se han iniciado.

PPL usa las clases de concurrency::task_group y de concurrency::structured_task_group para representar grupos de tareas, y la clase de concurrency::task_handle para representar las tareas que se ejecutan en estos grupos.La clase task_handle encapsula el código que realiza el trabajo.Como la clase de task , la función de trabajo tiene forma de una función lambda, un puntero a función, o un objeto de función.Normalmente no es necesario trabajar con objetos task_handle directamente.En su lugar, se pasan las funciones de trabajo a un grupo de tareas, y el grupo de tareas crea y administra los objetos task_handle.

PPL divide los grupos de tareas en estas dos categorías: grupos de tareas no estructurados y grupos de tareas estructurados.PPL usa la clase de task_group para representar grupos de tareas no estructurados y la clase de structured_task_group para representar grupos de tareas estructurados.

Nota importanteImportante

PPL también define el algoritmo de concurrency::parallel_invoke , que utiliza la clase de structured_task_group para ejecutar un conjunto de tareas en paralelo.Como el algoritmo parallel_invoke tiene una sintaxis más concisa, se recomienda usarla en lugar de la clase structured_task_group si es posible.En el tema Algoritmos paralelos se describe con mayor detalle parallel_invoke.

Use parallel_invoke cuando tenga varias tareas independientes que desee ejecutar al mismo tiempo y deba esperar a que todas las tareas finalicen antes de continuar.Esta técnica suele denominarse paralelismo de bifurcación y unión .Use task_group cuando tenga varias tareas independientes que desee ejecutar al mismo tiempo, pero desee esperar hasta que las tareas finalicen posteriormente.Por ejemplo, puede agregar tareas a un objeto task_group y esperar hasta que las tareas finalicen en otra función o desde otro subproceso.

Los grupos de tareas admiten el concepto de cancelación.La cancelación permite señalar a todas las tareas activas que desea cancelar la operación en su conjunto.La cancelación también impide que se inicien las tareas que todavía no se hayan iniciado.Para obtener más información sobre la cancelación, vea Cancelación en la biblioteca PPL.

El runtime también proporciona un modelo de control de excepciones que permite producir una excepción desde una tarea y controlar esa excepción cuando se espera hasta que finaliza el grupo de tareas asociado.Para obtener más información sobre este modelo de control de excepciones, vea Control de excepciones en el runtime de simultaneidad.

[parte superior]

Comparación entre task_group y structured_task_group

Aunque se recomienda task_group o parallel_invoke en lugar de la clase de structured_task_group , hay casos donde desea utilizar structured_task_group, por ejemplo, cuando se escribe un algoritmo paralelo que realiza un número variable de tareas o requiere la compatibilidad con la cancelación.En esta sección se explican las diferencias entre las clases task_group y structured_task_group.

La clase task_group es segura para subprocesos.Por consiguiente puede agregar tareas a un objeto de task_group desde varios subprocesos y esperar o cancelar un objeto de task_group de varios subprocesos.La creación y destrucción de un objeto structured_task_group deben realizarse en el mismo ámbito léxico.Además, todas las operaciones sobre un objeto structured_task_group deben realizarse en el mismo subproceso.La excepción a esta regla son métodos de concurrency::structured_task_group::cancel y de el concurrency::structured_task_group::is_canceling .Una tarea secundaria puede llamar a estos métodos para cancelar el grupo de tareas primario o comprobar la cancelación en cualquier momento.

Puede ejecutar tareas adicionales en un objeto de task_group después de llamar al método de concurrency::task_group::wait o de concurrency::task_group::run_and_wait .A la inversa, no puede ejecutar tareas adicionales en un objeto de structured_task_group después de llamar a los métodos de concurrency::structured_task_group::wait o de concurrency::structured_task_group::run_and_wait .

Puesto que la clase structured_task_group no se sincronizado entre subprocesos, tiene menos sobrecarga de ejecución que la clase task_group.Por tanto, si el problema no necesita que se programe trabajo desde varios subprocesos y no se puede usar el algoritmo parallel_invoke, la clase structured_task_group puede ayudarle a escribir código con mejor rendimiento.

Si usa un objeto structured_task_group dentro de otro objeto structured_task_group, el objeto interno debe finalizar y ser destruido antes de que el objeto externo finalizado.La clase task_group no necesita que los grupos de tareas anidadas finalicen antes de que finalice el grupo externo.

Los grupos de tareas no estructurados y los grupos de tareas estructurados usan los identificadores de tareas de maneras diferentes.Puede pasar funciones de trabajo directamente a un objeto task_group; el objeto task_group creará y administrará el identificador de tareas.La clase structured_task_group necesita administrar un objeto task_handle por cada tarea.Cada objeto task_handle debe seguir siendo válido mientras dure su objeto structured_task_group asociado.Utilice la función de concurrency::make_task para crear un objeto de task_handle , como se muestra en el siguiente ejemplo básico:

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

Para administrar los identificadores de tareas para aquellos casos en los que tiene un número variable de tareas, use una rutina de asignación de pila como _malloca o una clase contenedora como std::vector.

Tanto task_group como structured_task_group admiten la cancelación.Para obtener más información sobre la cancelación, vea Cancelación en la biblioteca PPL.

[parte superior]

Ejemplo

En el siguiente ejemplo básico se muestra cómo trabajar con grupos de tareas.En este ejemplo se usa el algoritmo parallel_invoke para realizar dos tareas simultáneamente.Cada tarea agrega subtareas a un objeto task_group.Observe que la clase task_group permite que varias tareas le agreguen tareas simultáneamente.

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

A continuación, se muestra la salida de este ejemplo:

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

Puesto que el algoritmo parallel_invoke ejecuta las tareas simultáneamente, el orden de los mensajes de salida podría variar.

Para obtener ejemplos completos en los que se muestra cómo usar el algoritmo parallel_invoke, vea Cómo: Usar parallel.invoke para escribir una rutina de ordenación en paralelo y Cómo: Usar parallel.invoke para ejecutar operaciones paralelas.Para obtener un ejemplo completo en el que se usa la clase task_group para implementar características asincrónicas, vea Tutorial: Implementar futuros.

[parte superior]

Programación sólida

Asegúrese de que entiende el rol de cancelación y el control de excepciones cuando use las tareas, los grupos de tareas, y los algoritmos paralelos.Por ejemplo, en un árbol de trabajo paralelo, una tarea que se cancela evita que se ejecuten las tareas secundarias.Esto puede producir problemas si una de las tareas secundarias realiza una operación que es importante para la aplicación, como liberar un recurso.Además, si una tarea secundaria produce una excepción, esa excepción podría propagarse a través de un destructor de objeto y provocar un comportamiento no definido en la aplicación.Para obtener un ejemplo que muestra estos puntos, vea la sección Understand how Cancellation and Exception Handling Affect Object Destruction del documento Procedimientos recomendados de la Biblioteca de modelos de procesamiento paralelo.Para obtener más información sobre los modelos de cancelación y control de excepciones en PPL, vea Cancelación en la biblioteca PPL y Control de excepciones en el runtime de simultaneidad.

[parte superior]

Temas relacionados

Título

Descripción

Cómo: Usar parallel.invoke para escribir una rutina de ordenación en paralelo

Muestra cómo usar el algoritmo parallel_invoke para mejorar el rendimiento del algoritmo de ordenación bitónica.

Cómo: Usar parallel.invoke para ejecutar operaciones paralelas

Muestra cómo usar el algoritmo parallel_invoke para mejorar el rendimiento de un programa que realiza varias operaciones en un origen de datos compartido.

Cómo: Crear una tarea que se complete después de un retardo

Muestra cómo utilizar task, cancellation_token_source, cancellation_token, y las clases de task_completion_event para crear una tarea que completa después de un retraso.

Tutorial: Implementar futuros

Muestra cómo combinar la funcionalidad existente en el Runtime de simultaneidad para obtener mejoras.

Parallel Patterns Library (PPL)

Describe la biblioteca PPL, que proporciona un modelo de programación imperativo para desarrollar aplicaciones simultáneas.

Referencia

task (Clase) (Motor en tiempo de ejecución de simultaneidad)

task_completion_event (Clase)

when_all (Función)

when_any (Función)

task_group (Clase)

parallel_invoke (Función)

structured_task_group (Clase)