Compartir a través de


Cancelación en la biblioteca PPL

En este documento se explica el rol de cancelación en la biblioteca (PPL) de modelos de procesamiento Paralelo, cómo cancelar el trabajo paralelo, y cómo determinar cuando el trabajo en paralelo se cancela.

[!NOTA]

El runtime usa el control de excepciones para implementar la cancelación.No debe detectar ni administrar estas excepciones en su código.Además, le recomendamos que escriba código seguro ante excepciones en los cuerpos de las funciones de las tareas.Por ejemplo, puede usar el modelo Resource Acquisition Is Initialization (RAII) para asegurarse de que los recursos se administran correctamente cuando se inicia una excepción en el cuerpo de una tarea.Si desea consultar un ejemplo completo en el que se usa el modelo RAII para limpiar un recurso en una tarea cancelable, vea Tutorial: Quitar trabajo de un subproceso de la interfaz de usuario.

Puntos clave

  • La cancelación es cooperativa implica la coordinación entre el código que solicita la cancelación y la tarea que responde a la cancelación.

  • Cuando sea posible, tokenes de cancelación de uso para cancelar el trabajo.La clase de concurrency::cancellation_token define un token de cancelación.

  • Cuando se utiliza tokenes de cancelación, utilice el método de concurrency::cancellation_token_source::cancel para iniciar la cancelación y concurrency::is_task_cancellation_requested y concurrency::cancel_current_task funciona para responder a la cancelación.

  • Cancelación no aparece inmediatamente.Aunque la nuevos no se inicie si una tarea o un grupo de tareas está cancelado, el trabajo activo deberá buscar y responder a la cancelación.

  • Una continuación siguiente valor basada hereda el token de cancelación de la tarea antecedente.Una continuación tarea-basada nunca hereda el token de su tarea antecedente.

  • Utilice el método de concurrency::cancellation_token::none cuando se llama a un constructor o función que toma un objeto de cancellation_token pero no desea que la operación para ser cancelable.Además, si no se pasa un token de cancelación al constructor de concurrency::task o a la función de concurrency::create_task , esa tarea no es cancelable.

En este documento

  • Árboles de trabajo paralelo

  • Cancelar tareas paralelas

    • Utilizando un token de Cancelación para cancelar Paralelo funcione

    • Usar el método cancel para cancelar el trabajo paralelo

    • Usar excepciones para cancelar el trabajo paralelo

  • Cancelar algoritmos paralelos

  • Cuándo no conviene usar la cancelación

Árboles de trabajo paralelo

PPL usa tareas y grupos de tareas para administrar tareas y cálculos específicos.Los grupos de tareas se pueden anidar para formar árboles de trabajo paralelo.En la ilustración siguiente se muestra un árbol de trabajo paralelo.En esta ilustración, tg1 y tg2 representan grupos de tareas; t1, t2, t3, t4, y t5 representan el trabajo que los grupos de tareas realizan.

Árbol de trabajo paralelo

En el ejemplo siguiente se muestra el código necesario para crear el árbol de la ilustración.En este ejemplo, tg1 y tg2 son objetos de concurrency::structured_task_group ; t1, t2, t3, t4, y t5 son objetos de concurrency::task_handle .

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

using namespace concurrency;
using namespace std;

void create_task_tree()
{   
   // Create a task group that serves as the root of the tree.
   structured_task_group tg1;

   // Create a task that contains a nested task group.
   auto t1 = make_task([&] {
      structured_task_group tg2;

      // Create a child task.
      auto t4 = make_task([&] {
         // TODO: Perform work here.
      });

      // Create a child task.
      auto t5 = make_task([&] {
         // TODO: Perform work here.
      });

      // Run the child tasks and wait for them to finish.
      tg2.run(t4);
      tg2.run(t5);
      tg2.wait();
   });

   // Create a child task.
   auto t2 = make_task([&] {
      // TODO: Perform work here.
   });

   // Create a child task.
   auto t3 = make_task([&] {
      // TODO: Perform work here.
   });

   // Run the child tasks and wait for them to finish.
   tg1.run(t1);
   tg1.run(t2);
   tg1.run(t3);
   tg1.wait();   
}

También puede utilizar la clase de concurrency::task_group para crear un árbol similar de trabajo.La clase de concurrency::task también admite la noción de un árbol de trabajo.Sin embargo, un árbol de task es un esquema lógico.En un árbol de task , los trabajos futuros completan después de trabajo actual.En un árbol de grupo de tareas, el trabajo interno completa antes de trabajo externo.Para obtener más información sobre las diferencias entre las tareas y los grupos de tareas, vea Paralelismo de tareas (Runtime de simultaneidad).

[parte superior]

Cancelar tareas paralelas

Hay varias maneras de cancelar el trabajo paralelo.La forma preferida es utilizar un token de cancelación.Los grupos de tareas también admiten el método de concurrency::task_group::cancel y el método de concurrency::structured_task_group::cancel .La forma final es producir una excepción en el cuerpo de una función de trabajo de tarea.Independientemente del método que elija, saber que la cancelación no aparece inmediatamente.Aunque la nuevos no se inicie si una tarea o un grupo de tareas está cancelado, el trabajo activo deberá buscar y responder a la cancelación.

Para obtener más ejemplos que se cancelan tareas paralelas, vea Tutorial: Conectar usando tareas y solicitud HTTP XML (IXHR2), Cómo: Usar la cancelación para interrumpir un bucle Parallel, y a Cómo: Usar el control de excepciones para interrumpir un bucle Parallel.

Dd984117.collapse_all(es-es,VS.110).gifUtilizando un token de Cancelación para cancelar Paralelo funcione

task, task_group, y admiten la cancelación con de las clases de structured_task_group con el uso de los tokenes de cancelación.PPL define las clases de concurrency::cancellation_token_source y de concurrency::cancellation_token con este fin.Cuando se usa un token de cancelación para cancelar el trabajo, el runtime no inicia el nuevo trabajo suscrito a ese token.Funcione que ya está activo puede controlar su token de cancelación y detener cuando puede.

Para iniciar la cancelación, llame al método de concurrency::cancellation_token_source::cancel .Sólo responderá a la cancelación de estas maneras:

  • Para los objetos de task , utilice las funciones de concurrency::is_task_cancellation_requested y de concurrency::cancel_current_task .cancel_current_task cancela la tarea actual y cualquiera de sus continuaciones siguiente valor basadas en.(No cancela el token de cancelación asociado a la tarea o sus continuaciones.)

  • Para los grupos de tareas y algoritmos paralelos, utilice la función de el concurrency::is_current_task_group_canceling para detectar la cancelación y cambiar lo más rápidamente posible body de la tarea cuando esta función devuelve true.(No llame a cancel_current_task de un grupo de tareas).

El ejemplo siguiente se muestra el primer modelo básico para la cancelación de la tarea.Comprueba el cuerpo de la tarea de vez en cuando la cancelación dentro de un bucle.

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

using namespace concurrency;
using namespace std;

bool do_work()
{
    // Simulate work.
    wcout << L"Performing work..." << endl;
    wait(250);
    return true;
}

int wmain()
{
    cancellation_token_source cts;
    auto token = cts.get_token();

    wcout << L"Creating task..." << endl;

    // Create a task that performs work until it is canceled.
    auto t = create_task([]
    {
        bool moreToDo = true;
        while (moreToDo)
        {
            // Check for cancellation.
            if (is_task_cancellation_requested())
            {
                // TODO: Perform any necessary cleanup here...

                // Cancel the current task.
                cancel_current_task();
            }
            else 
            {
                // Perform work.
                moreToDo = do_work();
            }
        }
    }, token);

    // Wait for one second and then cancel the task.
    wait(1000);

    wcout << L"Canceling task..." << endl;
    cts.cancel();

    // Wait for the task to cancel.
    wcout << L"Waiting for task to complete..." << endl;
    t.wait();

    wcout << L"Done." << endl;
}

/* Sample output:
    Creating task...
    Performing work...
    Performing work...
    Performing work...
    Performing work...
    Canceling task...
    Waiting for task to complete...
    Done.
*/

Los tiros de la función de cancel_current_task ; por consiguiente, no necesita explícitamente volver de la cresta de actual o de la función.

SugerenciaSugerencia

Alternativamente, puede llamar a la función de concurrency::interruption_point en lugar de is_task_cancellation_requested y de cancel_current_task.

Es importante llamar a cancel_current_task cuando sólo responderá a la cancelación porque pase la tarea en el estado cancelado.Si vuelve a en lugar de la llamada cancel_current_task, cambia de la operación al estado completado y cualquier continuación siguiente valor basada se ejecutan.

Nota de precauciónPrecaución

Nunca produce task_canceled del código.Llamada cancel_current_task en su lugar.

Cuando finaliza una tarea en el estado cancelado, el método de concurrency::task::get producen concurrency::task_canceled.(Al contrario, concurrency::task::wait devuelve task_status::canceled y no se produce.) El ejemplo siguiente se muestra este comportamiento para una continuación tarea- basada en.Una continuación tarea- basada siempre se llama, incluso cuando la tarea antecedente se cancela.

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

using namespace concurrency;
using namespace std;

int wmain()
{
    auto t1 = create_task([]() -> int
    {
        // Cancel the task.
        cancel_current_task();
    });

    // Create a continuation that retrieves the value from the previous.
    auto t2 = t1.then([](task<int> t)
    {
        try
        {
            int n = t.get();
            wcout << L"The previous task returned " << n << L'.' << endl;
        }
        catch (const task_canceled& e)
        {
            wcout << L"The previous task was canceled." << endl;
        }
    });

    // Wait for all tasks to complete.
    t2.wait();
}
/* Output:
    The previous task was canceled.
*/

Dado que las continuaciones siguiente valor basadas heredan el token de la tarea antecedente a menos que se crearon con un token explícita, las continuaciones inmediatamente entran en el estado cancelado incluso cuando la tarea anterior todavía se está ejecutando.Por consiguiente, cualquier excepción producida por la tarea antecedente después de la eliminación no se propaga a la continuación encarga.Cancelación siempre invalida el estado de la tarea antecedente.El ejemplo siguiente es similar al anterior, pero muestra el comportamiento de una continuación siguiente valor basada en.

auto t1 = create_task([]() -> int
{
    // Cancel the task.
    cancel_current_task();
});

// Create a continuation that retrieves the value from the previous.
auto t2 = t1.then([](int n)
{
    wcout << L"The previous task returned " << n << L'.' << endl;
});

try
{
    // Wait for all tasks to complete.
    t2.get();
}
catch (const task_canceled& e)
{
    wcout << L"The task was canceled." << endl;
}
/* Output:
    The task was canceled.
*/
Nota de precauciónPrecaución

Si no pasa un token de cancelación al constructor de task o a la función de concurrency::create_task , esa tarea no es cancelable.Además, debe pasar el mismo token de cancelación al constructor de cualquier tarea anidada (es decir, tareas que se crea en el cuerpo de otra tarea) de cancelar todas las tareas simultáneamente.

Quizás desee ejecutar código arbitrario cuando un token de cancelación se cancela.Por ejemplo, si el usuario elige un botón de Cancelar en la interfaz de usuario para cancelar la operación, puede deshabilitar el botón hasta que el usuario inicie otra operación.El ejemplo siguiente se muestra cómo utilizar el método de concurrency::cancellation_token::register_callback para registrar una función de devolución de llamada que se ejecuta cuando un token de cancelación se cancela.

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

using namespace concurrency;
using namespace std;

int wmain()
{
    cancellation_token_source cts;
    auto token = cts.get_token();

    // An event that is set in the cancellation callback.
    event e;

    cancellation_token_registration cookie;
    cookie = token.register_callback([&e, token, &cookie]()
    {
        wcout << L"In cancellation callback..." << endl;
        e.set();

        // Although not required, demonstrate how to unregister 
        // the callback.
        token.deregister_callback(cookie);
    });

    wcout << L"Creating task..." << endl;

    // Create a task that waits to be canceled.
    auto t = create_task([&e]
    {
        e.wait();
    }, token);

    // Cancel the task.
    wcout << L"Canceling task..." << endl;
    cts.cancel();

    // Wait for the task to cancel.
    t.wait();

    wcout << L"Done." << endl;
}
/* Sample output:
    Creating task...
    Canceling task...
    In cancellation callback...
    Done.
*/

El documento Paralelismo de tareas (Runtime de simultaneidad) explica la diferencia entre las continuaciones siguiente valor basadas y tarea- basadas en.Si no proporciona un objeto de cancellation_token a una tarea de continuación, la continuación hereda el token de cancelación de la tarea antecedente de las maneras siguientes:

  • Una continuación siguiente valor basada hereda siempre el token de cancelación de la tarea antecedente.

  • Una continuación tarea- basada nunca hereda el token de cancelación de la tarea antecedente.La única manera de crear una continuación tarea- basada cancelable es pasar explícitamente un token de cancelación.

Estos comportamientos no afectados por una tarea errores (es decir, una que produce una excepción).En este caso, una continuación siguiente valor basada se cancela; una continuación tarea- basada no se cancela.

Nota de precauciónPrecaución

Una tarea que se crea en otra tarea (es decir una tarea anidada) no hereda el token de cancelación de la tarea primaria.Sólo una continuación siguiente valor basada hereda el token de cancelación de la tarea antecedente.

SugerenciaSugerencia

Utilice el método de concurrency::cancellation_token::none cuando se llama a un constructor o función que toma un objeto de cancellation_token y no desea la operación para ser cancelable.

También puede proporcionar un token de cancelación al constructor de un objeto de task_group o de structured_task_group .Un aspecto importante de esto es que los grupos de tareas secundarios heredan este token de cancelación.Para obtener un ejemplo que muestre este concepto mediante la función de concurrency::run_with_cancellation_token a la ejecución para llamar a parallel_for, vea Cancelar algoritmos paralelos más adelante en este documento.

[parte superior]

Dd984117.collapse_all(es-es,VS.110).gifTokenes y tarea Composición de cancelación

Las funciones de concurrency::when_all y de concurrency::when_any pueden ayudarle a crear varias tareas para implementar modelos comunes.En esta sección se describe cómo estas funciones ejecutan los tokenes de cancelación.

Cuando se proporciona un token de cancelación a cualquier función de when_all y de when_any , las cancelaciones de esa función sólo cuando el token de cancelación se cancela o cuando uno de los extremos de tareas de participante en una cancelado o produce una excepción.

La función de when_all hereda el token de cancelación de cada tarea que constituyen la operación global cuando no proporciona ningún token de cancelación en el.La tarea que se devuelve de when_all se cancela cuando cualquiera de estos tokenes se cancelan y al menos uno de participante las tareas que aún no se han iniciado ni que ejecutan.Un comportamiento similar se produce cuando una de las tareas produce una excepción – la tarea que se devuelve de when_all inmediatamente se cancela con esa excepción.

El runtime elige el token de cancelación para la tarea que se devuelve de la función de when_any cuando esa tarea completa.Si cualquiera de final de las tareas de participante en un estado completa y uno o más de las tareas producen una excepción, una de las tareas que produjo se elige para completar when_any y el token se elige como el token para la tarea final.Si finals de más de una tarea en el estado completado, la tarea que se devuelve de extremos de tarea de when_any en un estado completado.Los intentos de runtime para elegir una tarea completa cuyo no cancela token a la hora de finalización para la tarea que se devuelve de when_any inmediatamente no se cancelarán aunque otras que se ejecutan pueden completar en un momento posterior.

[parte superior]

Dd984117.collapse_all(es-es,VS.110).gifUsar el método cancel para cancelar el trabajo paralelo

Los métodos de concurrency::task_group::cancel y de concurrency::structured_task_group::cancel establecen un grupo de tareas en el estado cancelado.Después de llamar a cancel, el grupo de tareas no iniciará ninguna otra tarea posterior.Los métodos cancel pueden invocarse a través de varias tareas secundarias.Una tarea cancelada hace que los métodos de concurrency::task_group::wait y de concurrency::structured_task_group::wait para devolver concurrency::canceled.

Si un grupo de tareas está cancelado, las llamadas de cada una de las tareas secundarias al runtime pueden activar un punto de interrupción, lo que hace que el runtime inicie y detecte un tipo de excepción interna para cancelar las tareas activas.El Runtime de simultaneidad no define puntos de interrupción concretos; estos pueden producirse en cualquier llamada al runtime.El runtime debe controlar las excepciones que se producen para poder llevar a cabo la cancelación.Por tanto, no deben controlarse excepciones desconocidas en el cuerpo de una tarea.

Si una tarea secundaria realiza una operación que exige mucho tiempo y no llama al runtime, debe comprobarse periódicamente si se he cancelado y si ha salido de forma puntual.En el ejemplo siguiente se muestra un mecanismo para determinar cuándo un trabajo está cancelado.La tarea t4 cancela el grupo de tareas primario cuando encuentra un error.La tarea t5 llama de tanto en tanto al método structured_task_group::is_canceling para comprobar si se ha cancelado.Si el grupo de tareas primario está cancelado, la tarea t5 imprime un mensaje y sale.

structured_task_group tg2;

// Create a child task.
auto t4 = make_task([&] {
   // Perform work in a loop.
   for (int i = 0; i < 1000; ++i)
   {
      // Call a function to perform work.
      // If the work function fails, cancel the parent task
      // and break from the loop.
      bool succeeded = work(i);
      if (!succeeded)
      {
         tg2.cancel();
         break;
      }
   }
});

// Create a child task.
auto t5 = make_task([&] {
   // Perform work in a loop.
   for (int i = 0; i < 1000; ++i)
   {
      // To reduce overhead, occasionally check for 
      // cancelation.
      if ((i%100) == 0)
      {
         if (tg2.is_canceling())
         {
            wcout << L"The task was canceled." << endl;
            break;
         }
      }

      // TODO: Perform work here.
   }
});

// Run the child tasks and wait for them to finish.
tg2.run(t4);
tg2.run(t5);
tg2.wait();

En este ejemplo se comprueba la cancelación cada vez que se producen 100 iteraciones del bucle de la tarea.La frecuencia con la que se comprueba la cancelación depende de la cantidad de trabajo que realiza la tarea y la rapidez necesaria con la que las tareas deben responder a la cancelación.

Si no tiene acceso al objeto del grupo de tareas primario, llame a la función de el concurrency::is_current_task_group_canceling para determinar si el grupo de tareas primario se cancela.

El método cancel solo afecta a las tareas secundarias.Por ejemplo, si se cancela el grupo de tareas tg1 de la ilustración del árbol de trabajo paralelo, todas las tareas del árbol (t1, t2, t3, t4 y t5) se verán afectadas.Si se cancela el grupo de tareas anidado tg2, solo las tareas t4 y t5 se verán afectadas.

Al llamar al método cancel, todos los grupos de tareas secundarios también se cancelan.Sin embargo, la cancelación no afecta a ningún elemento primario del grupo de tareas de un árbol de trabajo paralelo.En los ejemplos siguientes se usa el árbol de trabajo paralelo de la ilustración para mostrar este comportamiento.

En el primero de estos ejemplos se crea una función de trabajo para la tarea t4, que es un elemento secundario del grupo de tareas tg2.La función de trabajo llama a la función work en un bucle.Si las llamadas a work no se realizan correctamente, la tarea cancela su grupo de tareas primario.Esto hace que el grupo de tareas tg2 adopte el estado cancelado, pero no cancela el grupo de tareas tg1.

auto t4 = make_task([&] {
   // Perform work in a loop.
   for (int i = 0; i < 1000; ++i)
   {
      // Call a function to perform work.
      // If the work function fails, cancel the parent task
      // and break from the loop.
      bool succeeded = work(i);
      if (!succeeded)
      {
         tg2.cancel();
         break;
      }
   }         
});

Este segundo ejemplo se parece el primero, salvo por el hecho de que la tarea cancela el grupo de tareas tg1.Esto afecta a todas las tareas del árbol (t1, t2, t3, t4y t5).

auto t4 = make_task([&] {
   // Perform work in a loop.
   for (int i = 0; i < 1000; ++i)
   {
      // Call a function to perform work.
      // If the work function fails, cancel all tasks in the tree.
      bool succeeded = work(i);
      if (!succeeded)
      {
         tg1.cancel();
         break;
      }
   }   
});

La clase structured_task_group no es segura para la ejecución de subprocesos.Por tanto, si una tarea secundaria llama a un método de su objeto structured_task_group primario, se produce un comportamiento no especificado.Las excepciones a esta regla son métodos de 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 y comprobación de primarios cancelación.

Nota de precauciónPrecaución

Aunque puede utilizar un token de cancelación para cancelar el trabajo realizado por un grupo de tareas que ejecuta como elemento secundario de un objeto de task , no puede utilizar métodos de task_group::cancel o de structured_task_group::cancel cancelar los objetos de task que se ejecutan en un grupo de tareas.

[parte superior]

Dd984117.collapse_all(es-es,VS.110).gifUsar excepciones para cancelar el trabajo paralelo

El uso de los tokenes de cancelación y el método de cancel son más eficaces que el control de excepciones en la cancelación de un árbol de trabajo paralelo.Los tokenes de cancelación y el método de cancel cancelar una tarea y cualquier tarea secundaria de forma descendente.Por el contrario, el control de excepciones funciona de manera ascendente y debe cancelar cada grupo de tareas secundario por separado a medida que la excepción se propaga hacia arriba.En el tema Control de excepciones en el runtime de simultaneidad se explica cómo Runtime de simultaneidad usa las excepciones para notificar errores.Sin embargo, no todas las excepciones indican un error.Por ejemplo, un algoritmo de búsqueda puede cancelar la tarea asociada cuando encuentra el resultado.Sin embargo, tal y como se mencionó anteriormente, el control de excepciones resulta menos eficaz que el método cancel para cancelar el trabajo paralelo.

Nota de precauciónPrecaución

Se recomienda usar excepciones para cancelar el trabajo paralelo sólo cuando sea necesario.Los tokenes de cancelación y los métodos de cancel del grupo de tareas son un error más eficaz y menos propenso.

Cuando se produce una excepción en el cuerpo de una función de trabajo que se pasa a un grupo de tareas, el runtime almacena esa excepción y calcula las referencias de la excepción en el contexto en el que se espera a que el grupo de tareas finalice.Como sucede con el método cancel, el runtime descarta cualquier tarea que no se haya iniciado todavía y no acepta nuevas tareas.

Este tercer ejemplo se parece al segundo, salvo en que la tarea t4 produce una excepción para cancelar el grupo de tareas tg2.En este ejemplo se usa un bloque try-catch para comprobar la cancelación cuando el grupo de tareas tg2 espera a que sus tareas secundarias finalicen.Al igual que en el primer ejemplo, esto hace que el grupo de tareas tg2 pase a tener el estado cancelado, pero no cancela el grupo de tareas tg1.

structured_task_group tg2;

// Create a child task.      
auto t4 = make_task([&] {
   // Perform work in a loop.
   for (int i = 0; i < 1000; ++i)
   {
      // Call a function to perform work.
      // If the work function fails, throw an exception to 
      // cancel the parent task.
      bool succeeded = work(i);
      if (!succeeded)
      {
         throw exception("The task failed");
      }
   }         
});

// Create a child task.
auto t5 = make_task([&] {
   // TODO: Perform work here.
});

// Run the child tasks.
tg2.run(t4);
tg2.run(t5);

// Wait for the tasks to finish. The runtime marshals any exception
// that occurs to the call to wait.
try
{
   tg2.wait();
}
catch (const exception& e)
{
   wcout << e.what() << endl;
}

En este cuarto ejemplo se usa el control de excepciones para cancelar todo el árbol de trabajo.En el ejemplo, la excepción se detecta cuando el grupo de tareas tg1 espera a que sus tareas secundarias finalicen y no cuando el grupo de tareas tg2 espera a sus tareas secundarias.Al igual que en el segundo ejemplo, esto hace que los dos grupos de tareas del árbol, tg1 y tg2, pasen a tener el estado cancelado.

// Run the child tasks.
tg1.run(t1);
tg1.run(t2);
tg1.run(t3);   

// Wait for the tasks to finish. The runtime marshals any exception
// that occurs to the call to wait.
try
{
   tg1.wait();
}
catch (const exception& e)
{
   wcout << e.what() << endl;
}

Como los métodos task_group::wait y structured_task_group::wait se inician cuando una tarea secundaria produce una excepción, no devuelven ningún valor.

[parte superior]

Cancelar algoritmos paralelos

Algoritmos paralelos en PPL, por ejemplo, parallel_for, compilación en grupos de tareas.Por tanto, pueden usarse muchas técnicas similares para cancelar un algoritmo paralelo.

En los ejemplos siguientes se muestran varios mecanismos para cancelar un algoritmo paralelo.

El ejemplo siguiente utiliza la función de run_with_cancellation_token para llamar al algoritmo de parallel_for .La función de run_with_cancellation_token toma un token de cancelación como argumento y llama a la función de trabajo proporcionada sincrónicamente.Dado que los algoritmos paralelos se compilan sobre tareas, heredan el token de cancelación de la tarea primaria.Por consiguiente, parallel_for puede responder a la cancelación.

// cancel-parallel-for.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>
#include <sstream>

using namespace concurrency;
using namespace std;

int wmain()
{
    // Call parallel_for in the context of a cancellation token.
    cancellation_token_source cts;
    run_with_cancellation_token([&cts]() 
    {
        // Print values to the console in parallel.
        parallel_for(0, 20, [&cts](int n)
        {
            // For demonstration, cancel the overall operation 
            // when n equals 11.
            if (n == 11)
            {
                cts.cancel();
            }
            // Otherwise, print the value.
            else
            {
                wstringstream ss;
                ss << n << endl;
                wcout << ss.str();
            }
        });
    }, cts.get_token());
}
/* Sample output:
    15
    16
    17
    10
    0
    18
    5
*/

El ejemplo siguiente se usa el método de concurrency::structured_task_group::run_and_wait para llamar al algoritmo de parallel_for .El método structured_task_group::run_and_wait espera a que la tarea proporcionada finalice.El objeto structured_task_group permite que la función de trabajo cancele la tarea.

// To enable cancelation, call parallel_for in a task group.
structured_task_group tg;

task_group_status status = tg.run_and_wait([&] {
   parallel_for(0, 100, [&](int i) {
      // Cancel the task when i is 50.
      if (i == 50)
      {
         tg.cancel();
      }
      else
      {
         // TODO: Perform work here.
      }
   });
});

// Print the task group status.
wcout << L"The task group status is: ";
switch (status)
{
case not_complete:
   wcout << L"not complete." << endl;
   break;
case completed:
   wcout << L"completed." << endl;
   break;
case canceled:
   wcout << L"canceled." << endl;
   break;
default:
   wcout << L"unknown." << endl;
   break;
}

Este ejemplo produce el siguiente resultado.

The task group status is: canceled.

En el siguiente ejemplo se usa el control de excepciones para cancelar un bucle parallel_for.El runtime calcula las referencias de la excepción en el contexto de la llamada.

try
{
   parallel_for(0, 100, [&](int i) {
      // Throw an exception to cancel the task when i is 50.
      if (i == 50)
      {
         throw i;
      }
      else
      {
         // TODO: Perform work here.
      }
   });
}
catch (int n)
{
   wcout << L"Caught " << n << endl;
}

Este ejemplo produce el siguiente resultado.

Caught 50

En el siguiente ejemplo se usa una marca booleana para coordinar la cancelación de un bucle parallel_for.En este ejemplo se ejecutan todas las tareas, ya que no se usa el método cancel ni el control de excepciones para cancelar el conjunto completo de tareas.Por tanto, esta técnica puede tener mayor sobrecarga computacional que un mecanismo de cancelación.

// Create a Boolean flag to coordinate cancelation.
bool canceled = false;

parallel_for(0, 100, [&](int i) {
   // For illustration, set the flag to cancel the task when i is 50.
   if (i == 50)
   {
      canceled = true;
   }

   // Perform work if the task is not canceled.
   if (!canceled)
   {
      // TODO: Perform work here.
   }
});

Cada uno de los métodos de cancelación tiene ventajas sobre los otros.Elija el método que mejor se ajuste a sus necesidades concretas.

[parte superior]

Cuándo no conviene usar la cancelación

El uso de la cancelación es adecuado cuando cada miembro de un grupo de tareas relacionadas puede salir de forma puntual.Sin embargo, hay algunos escenarios en los que la cancelación podría no resultar adecuada para su aplicación.Por ejemplo, dado que la cancelación de tareas es cooperativa, el conjunto completo de tareas no se cancelará si alguna tarea individual está bloqueada.Por ejemplo, si una tarea no se ha iniciado todavía pero desbloquea otra tarea activa, no se iniciará si el grupo de tareas está cancelado.Esto puede generar una situación de interbloqueo en la aplicación.Un segundo ejemplo donde el uso de la cancelación puede no ser adecuado es cuando se cancela una tarea, pero su tarea secundaria realiza una operación importante, como liberar un recurso.Dado que el conjunto completo de tareas se cancela a la vez que la tarea primaria, esa operación no se ejecutará.Para obtener un ejemplo que muestre este punto, vea la sección Understand how Cancellation and Exception Handling Affect Object Destruction en las mejores prácticas del tema Biblioteca de modelos de procesamiento paralelo.

[parte superior]

Temas relacionados

Título

Descripción

Cómo: Usar la cancelación para interrumpir un bucle Parallel

Muestra cómo se usa la cancelación para implementar un algoritmo de búsqueda paralelo.

Cómo: Usar el control de excepciones para interrumpir un bucle Parallel

Muestra cómo utilizar la clase de task_group para escribir un algoritmo de búsqueda en una estructura de árbol básica.

Control de excepciones en el runtime de simultaneidad

Describe cómo el runtime controla las excepciones generadas por grupos de tareas, tareas ligeras y agentes asincrónicos y cómo se responde a las excepciones en las aplicaciones.

Paralelismo de tareas (Runtime de simultaneidad)

Describe cómo se relacionan las tareas con los grupos de tareas y cómo se pueden usar tareas estructuradas y no estructuradas en las aplicaciones.

Algoritmos paralelos

Describe los algoritmos paralelos, que realizan el trabajo de forma simultánea en colecciones de datos.

Parallel Patterns Library (PPL)

Proporciona información general sobre la Biblioteca de modelos de procesamiento paralelo.

Referencia

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

cancellation_token_source (Clase)

cancellation_token (Clase)

task_group (Clase)

structured_task_group (Clase)

parallel_for (Función)