I would use TPL Dataflow for this (since you are using .NET 4.5 and using Task internally). You can easily create an ActionBlock<TInput> that sends elements to itself after it has processed this action and waited for the appropriate amount of time.
First create a factory that will create your endless task:
ITargetBlock<DateTimeOffset> CreateNeverEndingTask( Action<DateTimeOffset> action, CancellationToken cancellationToken) {
I chose ActionBlock<TInput> to create the DateTimeOffset structure; you need to pass a type parameter, and it can also pass some useful state (you can change the nature of the state if you want).
Also note that the ActionBlock<TInput> only processes one element at a time, so you are guaranteed that only one action will be processed (which means you don't have to deal with reentrancy when it calls the Post method on its own) .
I also passed the CancellationToken structure to both the ActionBlock<TInput> constructor and the ActionBlock<TInput> constructor call method ; if the process is canceled, the cancellation will be performed as soon as possible.
From there, this is a simple refactoring of your code to store the ITargetBlock<DateTimeoffset> interface implemented by ActionBlock<TInput> (this is a higher level abstraction representing consumer blocks and you want to be able to trigger consumption by calling the Post extension method):
CancellationTokenSource wtoken; ActionBlock<DateTimeOffset> task;
Your StartWork :
void StartWork() {
And then your StopWork method:
void StopWork() {
Why do you want to use TPL Dataflow here? A few reasons:
Separation of problems
Now the CreateNeverEndingTask method is a factory that creates your "service", so to speak. You control when it starts and stops, and it is completely self-sufficient. You do not need to interweave timer status monitoring with other aspects of your code. You simply create a block, start it and stop when you are done.
More efficient use of threads / tasks / resources
The default scheduler for blocks in a TPL data stream is the same for Task , which is a thread pool. Using the ActionBlock<TInput> to handle your action, as well as calling Task.Delay , you gain control over the thread that you used when you were actually doing nothing. Of course, this leads to some overhead when you create a new Task that will handle the continuation, but this should be small, given that you do not process this in a narrow loop (you expect ten seconds between calls).
If the DoWork function DoWork really be made expected (namely, that it returns a Task ), then you can (possibly) optimize it even more by changing the factory method above to take Func<DateTimeOffset, CancellationToken, Task> instead of Action<DateTimeOffset> , eg:
ITargetBlock<DateTimeOffset> CreateNeverEndingTask( Func<DateTimeOffset, CancellationToken, Task> action, CancellationToken cancellationToken) {
Of course, it would be good practice to weave the CancellationToken using your method (if it accepts it), which is done here.
This means that you will have a DoWorkAsync method with the following signature:
Task DoWorkAsync(CancellationToken cancellationToken);
You would need to change (just a little, and you don't StartWork off problem sharing) the StartWork method to account for the new signature passed to the CreateNeverEndingTask method, for example:
void StartWork() {