Compartir a través de


Procedimiento Crear una tarea que se complete después de un retardo

En este ejemplo se explica cómo usar las clases concurrency::task, concurrency::cancellation_token_source, concurrency::cancellation_token, concurrency::task_completion_event, concurrency::timer y concurrency::call para crear una tarea que se completa después de un retraso. Puede usar este método para crear bucles que sondean ocasionalmente los datos. También puede especificar tiempos de espera, el control de retrasos de entradas del usuario durante un tiempo predeterminado, etc.

Ejemplo: las funciones complete_after y cancel_after_timeout

En el ejemplo siguiente se muestran las funciones complete_after y cancel_after_timeout. La función complete_after crea un objeto task que se completa después del retraso especificado. Usa un objeto timer y un objeto call para establecer un objeto task_completion_event después del retraso especificado. Mediante el uso de la clase task_completion_event, puede definir una tarea que se complete tan pronto como un subproceso u otra tarea indique que un valor está disponible. Cuando se establece el evento, las tareas del agente de escucha finalizan y sus continuaciones se programan para ejecutarse.

Sugerencia

Para más información sobre las clases timer y call, que forman parte de la biblioteca de agentes asincrónicos, consulte Bloques de mensajes asincrónicos.

La función cancel_after_timeout se basa en la función complete_after para cancelar una tarea si esa tarea no se completa antes de un tiempo de espera determinado. La función cancel_after_timeout crea dos tareas. La primera tarea indica que se ha realizado correctamente y se completa una vez completada la tarea proporcionada. La segunda tarea indica un error y se completa después del tiempo de espera especificado. La función cancel_after_timeout crea una tarea de continuación que se ejecuta cuando se completa la tarea correctamente o con error. Si la tarea con error se completa primero, la continuación cancela el origen del token para cancelar la tarea general.

// Creates a task that completes after the specified delay.
task<void> complete_after(unsigned int timeout)
{
    // A task completion event that is set when a timer fires.
    task_completion_event<void> tce;

    // Create a non-repeating timer.
    auto fire_once = new timer<int>(timeout, 0, nullptr, false);
    // Create a call object that sets the completion event after the timer fires.
    auto callback = new call<int>([tce](int)
    {
        tce.set();
    });

    // Connect the timer to the callback and start the timer.
    fire_once->link_target(callback);
    fire_once->start();

    // Create a task that completes after the completion event is set.
    task<void> event_set(tce);

    // Create a continuation task that cleans up resources and
    // and return that continuation task.
    return event_set.then([callback, fire_once]()
    {
        delete callback;
        delete fire_once;
    });
}

// Cancels the provided task after the specifed delay, if the task
// did not complete.
template<typename T>
task<T> cancel_after_timeout(task<T> t, cancellation_token_source cts, unsigned int timeout)
{
    // Create a task that returns true after the specified task completes.
    task<bool> success_task = t.then([](T)
    {
        return true;
    });
    // Create a task that returns false after the specified timeout.
    task<bool> failure_task = complete_after(timeout).then([]
    {
        return false;
    });

    // Create a continuation task that cancels the overall task 
    // if the timeout task finishes first.
    return (failure_task || success_task).then([t, cts](bool success)
    {
        if(!success)
        {
            // Set the cancellation token. The task that is passed as the
            // t parameter should respond to the cancellation and stop
            // as soon as it can.
            cts.cancel();
        }

        // Return the original task.
        return t;
    });
}

Ejemplo: Recuento de proceso de números primos

En el siguiente ejemplo se calcula varias veces el recuento de números primos de un intervalo [0, 100000]. Se produce un error en la operación si no se completa en un límite de tiempo determinado. La función count_primes muestra cómo utilizar la función cancel_after_timeout. Cuenta el número de primos en el intervalo especificado y produce un error si la tarea no se completa en el tiempo proporcionado. La función wmain llama a la función count_primes varias veces. Cada vez, reduce a la mitad el límite de tiempo. El programa finaliza si la operación no se completa en el límite de tiempo actual.

// Determines whether the input value is prime.
bool is_prime(int n)
{
    if (n < 2)
        return false;
    for (int i = 2; i < n; ++i)
    {
        if ((n % i) == 0)
            return false;
    }
    return true;
}

// Counts the number of primes in the range [0, max_value].
// The operation fails if it exceeds the specified timeout.
bool count_primes(unsigned int max_value, unsigned int timeout)
{
    cancellation_token_source cts;

    // Create a task that computes the count of prime numbers.
    // The task is canceled after the specified timeout.
    auto t = cancel_after_timeout(task<size_t>([max_value, timeout, cts]
    {
        combinable<size_t> counts;
        parallel_for<unsigned int>(0, max_value + 1, [&counts, cts](unsigned int n) 
        {
            // Respond if the overall task is cancelled by canceling 
            // the current task.
            if (cts.get_token().is_canceled())
            {
                cancel_current_task();
            }
            // NOTE: You can replace the calls to is_canceled
            // and cancel_current_task with a call to interruption_point.
            // interruption_point();

            // Increment the local counter if the value is prime.
            if (is_prime(n))
            {
                counts.local()++;
            }
        });
        // Return the sum of counts across all threads.
        return counts.combine(plus<size_t>());
    }, cts.get_token()), cts, timeout);

    // Print the result.
    try
    {
        auto primes = t.get();
        wcout << L"Found " << primes << L" prime numbers within " 
              << timeout << L" ms." << endl;
        return true;
    }
    catch (const task_canceled&)
    {
        wcout << L"The task timed out." << endl;
        return false;
    }
}

int wmain()
{
    // Compute the count of prime numbers in the range [0, 100000] 
    // until the operation fails.
    // Each time the test succeeds, the time limit is halved.

    unsigned int max = 100000;
    unsigned int timeout = 5000;
    
    bool success = true;
    do
    {
        success = count_primes(max, timeout);
        timeout /= 2;
    } while (success);
}
/* Sample output:
    Found 9592 prime numbers within 5000 ms.
    Found 9592 prime numbers within 2500 ms.
    Found 9592 prime numbers within 1250 ms.
    Found 9592 prime numbers within 625 ms.
    The task timed out.
*/

Cuando se usa esta técnica para cancelar las tareas después de un retraso, las tareas no iniciadas no se iniciarán después de cancelar la tarea general. Sin embargo, es importante que las tareas de larga duración respondan rápidamente a la cancelación. Para más información sobre la cancelación de tareas, consulte Cancelación en la biblioteca PPL.

Finalización del ejemplo de código

Este es el código completo de este ejemplo:

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

using namespace concurrency;
using namespace std;

// Creates a task that completes after the specified delay.
task<void> complete_after(unsigned int timeout)
{
    // A task completion event that is set when a timer fires.
    task_completion_event<void> tce;

    // Create a non-repeating timer.
    auto fire_once = new timer<int>(timeout, 0, nullptr, false);
    // Create a call object that sets the completion event after the timer fires.
    auto callback = new call<int>([tce](int)
    {
        tce.set();
    });

    // Connect the timer to the callback and start the timer.
    fire_once->link_target(callback);
    fire_once->start();

    // Create a task that completes after the completion event is set.
    task<void> event_set(tce);

    // Create a continuation task that cleans up resources and
    // and return that continuation task.
    return event_set.then([callback, fire_once]()
    {
        delete callback;
        delete fire_once;
    });
}

// Cancels the provided task after the specifed delay, if the task
// did not complete.
template<typename T>
task<T> cancel_after_timeout(task<T> t, cancellation_token_source cts, unsigned int timeout)
{
    // Create a task that returns true after the specified task completes.
    task<bool> success_task = t.then([](T)
    {
        return true;
    });
    // Create a task that returns false after the specified timeout.
    task<bool> failure_task = complete_after(timeout).then([]
    {
        return false;
    });

    // Create a continuation task that cancels the overall task 
    // if the timeout task finishes first.
    return (failure_task || success_task).then([t, cts](bool success)
    {
        if(!success)
        {
            // Set the cancellation token. The task that is passed as the
            // t parameter should respond to the cancellation and stop
            // as soon as it can.
            cts.cancel();
        }

        // Return the original task.
        return t;
    });
}

// Determines whether the input value is prime.
bool is_prime(int n)
{
    if (n < 2)
        return false;
    for (int i = 2; i < n; ++i)
    {
        if ((n % i) == 0)
            return false;
    }
    return true;
}

// Counts the number of primes in the range [0, max_value].
// The operation fails if it exceeds the specified timeout.
bool count_primes(unsigned int max_value, unsigned int timeout)
{
    cancellation_token_source cts;

    // Create a task that computes the count of prime numbers.
    // The task is canceled after the specified timeout.
    auto t = cancel_after_timeout(task<size_t>([max_value, timeout, cts]
    {
        combinable<size_t> counts;
        parallel_for<unsigned int>(0, max_value + 1, [&counts, cts](unsigned int n) 
        {
            // Respond if the overall task is cancelled by canceling 
            // the current task.
            if (cts.get_token().is_canceled())
            {
                cancel_current_task();
            }
            // NOTE: You can replace the calls to is_canceled
            // and cancel_current_task with a call to interruption_point.
            // interruption_point();

            // Increment the local counter if the value is prime.
            if (is_prime(n))
            {
                counts.local()++;
            }
        });
        // Return the sum of counts across all threads.
        return counts.combine(plus<size_t>());
    }, cts.get_token()), cts, timeout);

    // Print the result.
    try
    {
        auto primes = t.get();
        wcout << L"Found " << primes << L" prime numbers within " 
              << timeout << L" ms." << endl;
        return true;
    }
    catch (const task_canceled&)
    {
        wcout << L"The task timed out." << endl;
        return false;
    }
}

int wmain()
{
    // Compute the count of prime numbers in the range [0, 100000] 
    // until the operation fails.
    // Each time the test succeeds, the time limit is halved.

    unsigned int max = 100000;
    unsigned int timeout = 5000;
    
    bool success = true;
    do
    {
        success = count_primes(max, timeout);
        timeout /= 2;
    } while (success);
}
/* Sample output:
    Found 9592 prime numbers within 5000 ms.
    Found 9592 prime numbers within 2500 ms.
    Found 9592 prime numbers within 1250 ms.
    Found 9592 prime numbers within 625 ms.
    The task timed out.
*/

Compilar el código

Para compilar el código, cópielo y péguelo en un proyecto de Visual Studio, o en un archivo denominado task-delay.cpp. Después, ejecute el comando siguiente en una ventana del símbolo del sistema de Visual Studio.

cl.exe /EHsc task-delay.cpp

Consulte también

Paralelismo de tareas
task (clase) (Runtime de simultaneidad)
cancellation_token_source (clase)
cancellation_token (clase)
task_completion_event (clase)
timer (clase)
call (clase)
Bloques de mensajes asincrónicos
Cancelación en la biblioteca PPL