Дотепер я використовував завдання LongRunning TPL для циклічної роботи з фоновим процесором замість таймера потоків, оскільки:
- завдання TPL підтримує скасування
- таймер потоків може запустити інший потік, поки програма вимикається, що спричиняє можливі проблеми з розпорядженими ресурсами
- шанс перевиконання: таймер потоків може запустити інший потік, поки попередній все ще обробляється через несподівану тривалу роботу (я знаю, це можна запобігти зупинкою та перезапуском таймера)
Однак рішення TPL завжди вимагає виділеного потоку, який не є необхідним під час очікування наступної дії (а це більшу частину часу). Я хотів би використати запропоноване рішення Джеффа для виконання циклічної роботи, пов'язаної з процесором, у фоновому режимі, оскільки йому потрібен потік пулу потоків лише тоді, коли потрібно виконати роботу, яка є кращою для масштабованості (особливо, коли інтервальний період великий).
Для досягнення цього я б запропонував 4 адаптації:
- Додайте
ConfigureAwait(false)
до, Task.Delay()
щоб виконати doWork
дію з потоком пулу потоків, інакшеdoWork
буде виконано з викличним потоком, що не є ідеєю паралелізму
- Дотримуйтесь шаблону скасування, кинувши TaskCanceledException (все ще потрібно?)
- Переслати Скасування, прийняте до
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);
}