J'utiliserais TPL Dataflow pour cela (puisque vous utilisez .NET 4.5 et qu'il utilise en Task
interne). Vous pouvez facilement créer un ActionBlock<TInput>
qui publie des éléments sur lui-même après avoir traité son action et attendu un laps de temps approprié.
Tout d'abord, créez une usine qui créera votre tâche sans fin:
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;
}
J'ai choisi le ActionBlock<TInput>
pour prendre une DateTimeOffset
structure ; vous devez passer un paramètre de type, et il pourrait aussi bien passer un état utile (vous pouvez changer la nature de l'état, si vous le souhaitez).
Notez également que, ActionBlock<TInput>
par défaut, ne traite qu'un seul élément à la fois, vous êtes donc assuré qu'une seule action sera traitée (ce qui signifie que vous n'aurez pas à gérer la réentrance quand il rappelle la Post
méthode d'extension sur elle-même).
J'ai également passé la CancellationToken
structure au constructeur du ActionBlock<TInput>
et à l' appel de Task.Delay
méthode ; si le processus est annulé, l'annulation aura lieu à la première occasion possible.
À partir de là, il s'agit d'un refactoring facile de votre code pour stocker l' ITargetBlock<DateTimeoffset>
interface implémentée par ActionBlock<TInput>
(c'est l'abstraction de niveau supérieur représentant les blocs qui sont des consommateurs, et vous voulez pouvoir déclencher la consommation via un appel à la Post
méthode d'extension):
CancellationTokenSource wtoken;
ActionBlock<DateTimeOffset> task;
Votre StartWork
méthode:
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);
}
Et puis votre StopWork
méthode:
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;
}
Pourquoi voudriez-vous utiliser TPL Dataflow ici? Quelques raisons:
Séparation des préoccupations
La CreateNeverEndingTask
méthode est maintenant une usine qui crée votre "service" pour ainsi dire. Vous contrôlez quand il démarre et s'arrête, et il est complètement autonome. Vous n'avez pas à associer le contrôle d'état de la minuterie à d'autres aspects de votre code. Vous créez simplement le bloc, démarrez-le et arrêtez-le lorsque vous avez terminé.
Utilisation plus efficace des threads / tâches / ressources
Le planificateur par défaut pour les blocs dans le flux de données TPL est le même pour a Task
, qui est le pool de threads. En utilisant le ActionBlock<TInput>
pour traiter votre action, ainsi qu'un appel à Task.Delay
, vous cédez le contrôle du thread que vous utilisiez lorsque vous ne faites rien. Certes, cela entraîne en fait une surcharge lorsque vous créez le nouveau Task
qui traitera la continuation, mais cela devrait être petit, étant donné que vous ne traitez pas cela dans une boucle serrée (vous attendez dix secondes entre les invocations).
Si la DoWork
fonction peut réellement être rendue attendable (c'est-à-dire qu'elle renvoie a Task
), vous pouvez (éventuellement) optimiser cela encore plus en modifiant la méthode d'usine ci-dessus pour prendre a Func<DateTimeOffset, CancellationToken, Task>
au lieu de an Action<DateTimeOffset>
, comme ceci:
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;
}
Bien sûr, il serait bon d'intégrer CancellationToken
votre méthode (si elle en accepte une), ce qui est fait ici.
Cela signifie que vous auriez alors une DoWorkAsync
méthode avec la signature suivante:
Task DoWorkAsync(CancellationToken cancellationToken);
Vous devrez changer (seulement légèrement, et vous ne saignez pas la séparation des préoccupations ici) la StartWork
méthode pour tenir compte de la nouvelle signature transmise à la CreateNeverEndingTask
méthode, comme ceci:
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);
}