Практическое руководство. Создание задачи, выполняемой после задержки
В этом примере показано, как использовать классы concurrency::task, concurrency::cancellation_token_source, concurrency::cancellation_token, concurrency::task_completion_event, concurrency::timer и concurrency::call для создания задачи, которая завершается после задержки. Этот метод можно использовать для создания циклов, которые иногда запрашивают данные, вводят тайм-ауты, задерживают обработку вводимых пользователем данных на заранее определенное время и т. д.
Пример
В следующем примере показаны функции complete_after и cancel_after_timeout. Функция complete_after создает объект task, который завершается после указанной задержки. Она использует объект timer и объект call для установки объекта task_completion_event после указанной задержки. С помощью класса task_completion_event можно определить задачи, которые завершаются после того, как поток или другая задача сообщает, что значение доступно. Когда событие установлено, задачи прослушивателя завершаются и их продолжения планируются для запуска.
Совет
Дополнительные сведения о классах timer и call, являющихся частью библиотеки асинхронных агентов, см. в разделе Асинхронные блоки сообщений.
Функция cancel_after_timeout основана на функции complete_after и используется для отмены задачи, если эта задача не завершается до истечения заданного тайм-аута. Функция cancel_after_timeout создает две задачи. Первая задача демонстрирует успех и завершается после завершения предоставленной задачи; вторая задача демонстрирует неудачу и завершается после истечения указанного времени ожидания. Функция cancel_after_timeout создает задачу продолжения, которая выполняется, когда успешная или неудачная задача завершается. Если неудачная задача завершается первой, продолжение отменяет источник токена для отмены всей задачи.
// 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;
});
}
В следующем примере вычисляется количество простых чисел в диапазоне [0, 100000] несколько раз. Операция завершается неудачей, если она не завершается в течение заданного времени. Функция count_primes демонстрирует, как использовать функцию cancel_after_timeout. Она подсчитывает число простых чисел в заданном диапазоне и завершается неудачей, если задача не завершается за предоставленное время. Функция wmain несколько раз вызывает функцию count_primes. Каждый раз она уменьшает ограничение по времени в два раза. Программа завершается после того, как операция не завершилась за текущее ограничение времени.
// 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]
{
combinable<size_t> counts;
parallel_for<unsigned int>(0, max_value + 1, [&counts](unsigned int n)
{
// Respond if the overall task is cancelled by canceling
// the current task.
if (is_task_cancellation_requested())
{
cancel_current_task();
}
// NOTE: You can replace the calls to is_task_cancellation_requested
// 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& e)
{
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.
*/
При использовании этого метода для отмены задачи после задержки, все незапущенные задачи не запускаются после того, как общая задача отменена. Однако имеет смысл для всех продолжительных задач реагировать на отмену своевременно. В этом примере метод count_primes вызывает функции concurrency::is_task_cancellation_requested и cancel_current_task, чтобы реагировать на отмену. (Кроме того, можно вызвать функцию concurrency::interruption_point). Дополнительные сведения об отмене задач см. в разделе Отмена в библиотеке параллельных шаблонов.
Ниже приведен полный код этого примера.
// task-delay.cpp
// compile with: /EHsc
#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]
{
combinable<size_t> counts;
parallel_for<unsigned int>(0, max_value + 1, [&counts](unsigned int n)
{
// Respond if the overall task is cancelled by canceling
// the current task.
if (is_task_cancellation_requested())
{
cancel_current_task();
}
// NOTE: You can replace the calls to is_task_cancellation_requested
// 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& e)
{
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.
*/
Компиляция кода
Чтобы скомпилировать код, скопируйте и вставьте его в проект Visual Studio или в файл с именем task-delay.cpp, затем выполните в окне командной строки Visual Studio следующую команду.
cl.exe /EHsc task-delay.cpp
См. также
Ссылки
Класс task (среда выполнения с параллелизмом)
Класс cancellation_token_source
Функция is_task_cancellation_requested