Я бы использовал для этого TPL Dataflow (поскольку вы используете .NET 4.5, и он используется для Taskвнутренних целей ). Вы можете легко создать объект, ActionBlock<TInput>который отправляет элементы самому себе после того, как он обработал свое действие и подождал соответствующее количество времени.
Сначала создайте фабрику, которая создаст вашу бесконечную задачу:
ITargetBlock<DateTimeOffset> CreateNeverEndingTask(
Action<DateTimeOffset> action, CancellationToken cancellationToken)
{
if (action == null) throw new ArgumentNullException("action");
ActionBlock<DateTimeOffset> block = null;
block = new ActionBlock<DateTimeOffset>(async now => {
action(now);
await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken).
ConfigureAwait(false);
block.Post(DateTimeOffset.Now);
}, new ExecutionDataflowBlockOptions {
CancellationToken = cancellationToken
});
return block;
}
Я выбрал, ActionBlock<TInput>чтобы взять DateTimeOffsetструктуру ; вы должны передать параметр типа, и он также может передать какое-то полезное состояние (вы можете изменить природу состояния, если хотите).
Также обратите внимание, что ActionBlock<TInput>по умолчанию обрабатывается только один элемент за раз, поэтому вам гарантируется, что будет обработано только одно действие (то есть вам не придется иметь дело с повторным входом, когда он вызывает Postметод расширения обратно на себя).
Я также передал CancellationTokenструктуру конструктору ActionBlock<TInput>и Task.Delayметоду вызов ; если процесс отменен, отмена будет произведена при первой возможности.
Отсюда можно легко выполнить рефакторинг вашего кода для сохранения ITargetBlock<DateTimeoffset>интерфейса, реализованного с помощью ActionBlock<TInput>(это абстракция более высокого уровня, представляющая блоки, которые являются потребителями, и вы хотите иметь возможность инициировать потребление посредством вызова Postметода расширения):
CancellationTokenSource wtoken;
ActionBlock<DateTimeOffset> task;
Ваш StartWorkметод:
void StartWork()
{
wtoken = new CancellationTokenSource();
task = CreateNeverEndingTask(now => DoWork(), wtoken.Token);
task.Post(DateTimeOffset.Now);
}
И тогда ваш StopWorkметод:
void StopWork()
{
using (wtoken)
{
wtoken.Cancel();
}
wtoken = null;
task = null;
}
Почему вы хотите использовать здесь TPL Dataflow? Несколько причин:
Разделение проблем
Теперь CreateNeverEndingTaskметод - это фабрика, которая, так сказать, создает вашу «службу». Вы контролируете, когда он запускается и останавливается, и он полностью самодостаточен. Вам не нужно переплетать управление состоянием таймера с другими аспектами вашего кода. Вы просто создаете блок, запускаете его и останавливаете, когда закончите.
Более эффективное использование потоков / задач / ресурсов
Планировщик по умолчанию для блоков в потоке данных TPL такой же для a Task, который является пулом потоков. Используя ActionBlock<TInput>для обработки вашего действия, а также его вызов Task.Delay, вы передаете контроль над потоком, который вы использовали, когда на самом деле ничего не делаете. Конечно, это на самом деле приводит к некоторым накладным расходам, когда вы создаете новое, Taskкоторое будет обрабатывать продолжение, но оно должно быть небольшим, учитывая, что вы не обрабатываете это в жестком цикле (вы ждете десять секунд между вызовами).
Если DoWorkфункцию действительно можно сделать ожидаемой (а именно, в том смысле, что она возвращает a Task), то вы можете (возможно) оптимизировать это еще больше, настроив метод factory выше, чтобы он принимал a Func<DateTimeOffset, CancellationToken, Task>вместо an Action<DateTimeOffset>, например:
ITargetBlock<DateTimeOffset> CreateNeverEndingTask(
Func<DateTimeOffset, CancellationToken, Task> action,
CancellationToken cancellationToken)
{
if (action == null) throw new ArgumentNullException("action");
ActionBlock<DateTimeOffset> block = null;
block = new ActionBlock<DateTimeOffset>(async now => {
await action(now, cancellationToken).
ConfigureAwait(false);
await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken).
ConfigureAwait(false);
block.Post(DateTimeOffset.Now);
}, new ExecutionDataflowBlockOptions {
CancellationToken = cancellationToken
});
return block;
}
Конечно, было бы неплохо проплести весь CancellationTokenсвой метод (если он его принимает), что и сделано здесь.
Это означает, что тогда у вас будет DoWorkAsyncметод со следующей подписью:
Task DoWorkAsync(CancellationToken cancellationToken);
Вам нужно будет изменить (совсем немного, и вы не потеряете здесь разделение проблем) StartWorkметод для учета новой подписи, переданной CreateNeverEndingTaskметоду, например:
void StartWork()
{
wtoken = new CancellationTokenSource();
task = CreateNeverEndingTask((now, ct) => DoWorkAsync(ct), wtoken.Token);
task.Post(DateTimeOffset.Now, wtoken.Token);
}