До сих пор я использовал задачу LongRunning TPL для циклической фоновой работы с привязкой к ЦП вместо таймера потоковой передачи, потому что:
- задача TPL поддерживает отмену
- таймер потоковой передачи может запустить другой поток, пока программа завершает работу, вызывая возможные проблемы с удаленными ресурсами
- вероятность переполнения: таймер потоковой передачи может запустить другой поток, в то время как предыдущий все еще обрабатывается из-за неожиданной длительной работы (я знаю, это можно предотвратить, остановив и перезапустив таймер)
Однако в решении TPL всегда требуется выделенный поток, в котором нет необходимости в ожидании следующего действия (что бывает в большинстве случаев). Я хотел бы использовать предложенное решение Джеффа для выполнения циклической работы с привязкой к ЦП в фоновом режиме, потому что ему нужен поток потокового пула только тогда, когда есть работа, которую нужно выполнить, что лучше для масштабируемости (особенно, когда период интервала большой).
Для этого я бы предложил 4 варианта:
- Добавить
ConfigureAwait(false)
к, Task.Delay()
чтобы выполнить doWork
действие в потоке пула потоков, иначе doWork
будет выполнено в вызывающем потоке, что не является идеей параллелизма
- Придерживайтесь шаблона отмены, создав исключение TaskCanceledException (все еще требуется?)
- Перенаправьте CancellationToken на,
doWork
чтобы он мог отменить задачу.
- Добавьте параметр типа object для предоставления информации о состоянии задачи (например, задачи TPL)
Насчет пункта 2 я не уверен, async await по-прежнему требует 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()
и ожидая его.
Ниже представлена улучшенная замена async \ 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);
}