Я бы использовал для этого 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);
}