До сих пор я использовал задание LongRunning TPL для циклической фоновой работы с ЦП вместо таймера потоков, потому что:
- задача TPL поддерживает отмену
- таймер потоков может запустить другой поток во время завершения работы программы, что может привести к возможным проблемам с удаленными ресурсами
- вероятность переполнения: таймер потоков может запустить другой поток, пока предыдущий все еще обрабатывается из-за неожиданной длительной работы (я знаю, это можно предотвратить, остановив и перезапустив таймер)
Однако решение TPL всегда запрашивает выделенный поток, который не требуется при ожидании следующего действия (что происходит большую часть времени). Я хотел бы использовать предложенное решение Джеффа для выполнения циклической работы, связанной с ЦП, в фоновом режиме, потому что ему нужен поток потоков пула только тогда, когда есть работа, которая лучше для масштабируемости (особенно когда интервал большой).
Для этого я бы предложил 4 варианта:
- Добавьте
ConfigureAwait(false)
к Task.Delay()
, чтобы выполнить действие doWork
в потоке пула потоков, в противном случае doWork
будет выполняться в вызывающем потоке, что не является идеей параллелизма
- Придерживайтесь шаблона отмены, создав исключение TaskCanceledException (все еще требуется?)
- Переслать CancellationToken на
doWork
, чтобы включить его для отмены задачи
- Добавить параметр типа объекта для предоставления информации о состоянии задачи (например, задача TPL)
По поводу пункта 2 Я не уверен, что для асинхронного ожидания все еще требуется TaskCanceledExecption или это просто наилучшая практика?
public static async Task Run(Action<object, CancellationToken> doWork, object taskState, TimeSpan period, CancellationToken cancellationToken)
{
do
{
await Task.Delay(period, cancellationToken).ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
doWork(taskState, cancellationToken);
}
while (true);
}
Пожалуйста, оставьте свои комментарии к предлагаемому решению ...
Обновление 2016-8-30
Приведенное выше решение не сразу вызывает doWork()
, а начинается с await Task.Delay().ConfigureAwait(false)
для достижения переключения потока для doWork()
. Приведенное ниже решение преодолевает эту проблему, заключая первый doWork()
вызов в Task.Run()
и ожидая его.
Ниже приведена улучшенная асинхронная \ await замена для Threading.Timer
, которая выполняет отменяемую циклическую работу и является масштабируемой (по сравнению с решением TPL), поскольку она не занимает ни одного потока в ожидании следующего действия.
Обратите внимание, что в отличие от таймера время ожидания (period
) постоянно, а не время цикла; время цикла - это сумма времени ожидания и продолжительности doWork()
, которая может варьироваться.
public static async Task Run(Action<object, CancellationToken> doWork, object taskState, TimeSpan period, CancellationToken cancellationToken)
{
await Task.Run(() => doWork(taskState, cancellationToken), cancellationToken).ConfigureAwait(false);
do
{
await Task.Delay(period, cancellationToken).ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
doWork(taskState, cancellationToken);
}
while (true);
}