我会为此使用TPL Dataflow(因为您使用的是.NET 4.5,它在内部使用Task)。您可以轻松地创建一个ActionBlock<TInput>,它会在处理完其操作并等待适当的时间后将项目发布给自己。
首先,创建一个工厂来创建你永无止境的任务:
ITargetBlock<DateTimeOffset> CreateNeverEndingTask(
Action<DateTimeOffset> action, CancellationToken cancellationToken)
{
// Validate parameters.
if (action == null) throw new ArgumentNullException("action");
// Declare the block variable, it needs to be captured.
ActionBlock<DateTimeOffset> block = null;
// Create the block, it will call itself, so
// you need to separate the declaration and
// the assignment.
// Async so you can wait easily when the
// delay comes.
block = new ActionBlock<DateTimeOffset>(async now => {
// Perform the action.
action(now);
// Wait.
await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken).
// Doing this here because synchronization context more than
// likely *doesn't* need to be captured for the continuation
// here. As a matter of fact, that would be downright
// dangerous.
ConfigureAwait(false);
// Post the action back to the block.
block.Post(DateTimeOffset.Now);
}, new ExecutionDataflowBlockOptions {
CancellationToken = cancellationToken
});
// Return the block.
return block;
}
我选择了ActionBlock<TInput> 来获取DateTimeOffset structure;你必须传递一个类型参数,它也可以传递一些有用的状态(如果你愿意,你可以改变状态的性质)。
另外,请注意ActionBlock<TInput> 默认情况下一次只处理 一个 项,因此您可以保证只处理一个操作(也就是说,您不必处理与reentrancy 一起调用Post extension method 本身)。
我还将CancellationToken structure 传递给ActionBlock<TInput> 的构造函数和Task.Delay method 调用;如果流程被取消,将在第一时间取消。
从那里,您可以轻松地重构代码以存储由ActionBlock<TInput> 实现的ITargetBlock<DateTimeoffset> interface(这是表示作为消费者的块的更高级别的抽象,您希望能够通过调用Post扩展方法):
CancellationTokenSource wtoken;
ActionBlock<DateTimeOffset> task;
您的StartWork 方法:
void StartWork()
{
// Create the token source.
wtoken = new CancellationTokenSource();
// Set the task.
task = CreateNeverEndingTask(now => DoWork(), wtoken.Token);
// Start the task. Post the time.
task.Post(DateTimeOffset.Now);
}
然后是你的StopWork 方法:
void StopWork()
{
// CancellationTokenSource implements IDisposable.
using (wtoken)
{
// Cancel. This will cancel the task.
wtoken.Cancel();
}
// Set everything to null, since the references
// are on the class level and keeping them around
// is holding onto invalid state.
wtoken = null;
task = null;
}
为什么要在这里使用 TPL 数据流?几个原因:
关注点分离
CreateNeverEndingTask 方法现在可以说是创建“服务”的工厂。您可以控制它何时启动和停止,它是完全独立的。您不必将计时器的状态控制与代码的其他方面交织在一起。您只需创建块、启动它并在完成后停止它。
更有效地使用线程/任务/资源
TPL 数据流中块的默认调度程序与Task 相同,即线程池。通过使用ActionBlock<TInput> 来处理您的操作,以及调用Task.Delay,您可以在您实际上没有做任何事情时让出对您正在使用的线程的控制。当然,当您生成将处理延续的新 Task 时,这实际上会导致一些开销,但这应该很小,考虑到您没有在一个紧密的循环中处理它(您在调用之间等待十秒钟) .
如果DoWork 函数实际上可以等待(即,它返回一个Task),那么您可以(可能)通过调整上面的工厂方法来进一步优化它以取而代之的是Func<DateTimeOffset, CancellationToken, Task> Action<DateTimeOffset>,像这样:
ITargetBlock<DateTimeOffset> CreateNeverEndingTask(
Func<DateTimeOffset, CancellationToken, Task> action,
CancellationToken cancellationToken)
{
// Validate parameters.
if (action == null) throw new ArgumentNullException("action");
// Declare the block variable, it needs to be captured.
ActionBlock<DateTimeOffset> block = null;
// Create the block, it will call itself, so
// you need to separate the declaration and
// the assignment.
// Async so you can wait easily when the
// delay comes.
block = new ActionBlock<DateTimeOffset>(async now => {
// Perform the action. Wait on the result.
await action(now, cancellationToken).
// Doing this here because synchronization context more than
// likely *doesn't* need to be captured for the continuation
// here. As a matter of fact, that would be downright
// dangerous.
ConfigureAwait(false);
// Wait.
await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken).
// Same as above.
ConfigureAwait(false);
// Post the action back to the block.
block.Post(DateTimeOffset.Now);
}, new ExecutionDataflowBlockOptions {
CancellationToken = cancellationToken
});
// Return the block.
return block;
}
当然,最好将CancellationToken 编织到您的方法(如果它接受一个),这在此处完成。
这意味着您将拥有一个带有以下签名的DoWorkAsync 方法:
Task DoWorkAsync(CancellationToken cancellationToken);
您必须更改(只是轻微地,并且您不会在这里放弃关注点分离)StartWork 方法来解释传递给 CreateNeverEndingTask 方法的新签名,如下所示:
void StartWork()
{
// Create the token source.
wtoken = new CancellationTokenSource();
// Set the task.
task = CreateNeverEndingTask((now, ct) => DoWorkAsync(ct), wtoken.Token);
// Start the task. Post the time.
task.Post(DateTimeOffset.Now, wtoken.Token);
}