Ainsi, mon application doit effectuer une action presque en continu (avec une pause d'environ 10 secondes entre chaque exécution) tant que l'application est en cours d'exécution ou qu'une annulation est demandée. Le travail qu'il doit effectuer peut prendre jusqu'à 30 secondes.
Est-il préférable d'utiliser un System.Timers.Timer et d'utiliser AutoReset pour s'assurer qu'il n'effectue pas l'action avant la fin du "tick" précédent.
Ou devrais-je utiliser une tâche générale en mode LongRunning avec un jeton d'annulation et avoir une boucle while infinie régulière à l'intérieur, appelant l'action effectuant le travail avec un Thread de 10 secondes. En ce qui concerne le modèle async / await, je ne suis pas sûr qu'il conviendrait ici car je n'ai aucune valeur de retour du travail.
CancellationTokenSource wtoken;
Task task;
void StopWork()
{
wtoken.Cancel();
try
{
task.Wait();
} catch(AggregateException) { }
}
void StartWork()
{
wtoken = new CancellationTokenSource();
task = Task.Factory.StartNew(() =>
{
while (true)
{
wtoken.Token.ThrowIfCancellationRequested();
DoWork();
Thread.Sleep(10000);
}
}, wtoken, TaskCreationOptions.LongRunning);
}
void DoWork()
{
// Some work that takes up to 30 seconds but isn't returning anything.
}
ou simplement utiliser une simple minuterie tout en utilisant sa propriété AutoReset, et appeler .Stop () pour l'annuler?
Réponses:
J'utiliserais TPL Dataflow pour cela (puisque vous utilisez .NET 4.5 et qu'il utilise en
Task
interne). Vous pouvez facilement créer unActionBlock<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:
J'ai choisi le
ActionBlock<TInput>
pour prendre uneDateTimeOffset
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 laPost
méthode d'extension sur elle-même).J'ai également passé la
CancellationToken
structure au constructeur duActionBlock<TInput>
et à l' appel deTask.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 parActionBlock<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 à laPost
méthode d'extension):Votre
StartWork
méthode:Et puis votre
StopWork
méthode: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 leActionBlock<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 nouveauTask
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 aTask
), vous pouvez (éventuellement) optimiser cela encore plus en modifiant la méthode d'usine ci-dessus pour prendre aFunc<DateTimeOffset, CancellationToken, Task>
au lieu de anAction<DateTimeOffset>
, comme ceci: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: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 à laCreateNeverEndingTask
méthode, comme ceci:la source
Je trouve que la nouvelle interface basée sur les tâches est très simple pour faire des choses comme celle-ci - encore plus facile que d'utiliser la classe Timer.
Il y a quelques petits ajustements que vous pouvez apporter à votre exemple. Au lieu de:
Tu peux le faire:
De cette façon, l'annulation se produira instantanément si à l'intérieur du
Task.Delay
, plutôt que d'avoir à attendre laThread.Sleep
fin.De plus, utiliser
Task.Delay
overThread.Sleep
signifie que vous n'attachez pas un fil à ne rien faire pendant la durée du sommeil.Si vous le pouvez, vous pouvez également faire
DoWork()
accepter un jeton d'annulation, et l'annulation sera beaucoup plus réactive.la source
Task.Run
utilise le pool de threads, donc votre exemple d'utilisationTask.Run
au lieu deTask.Factory.StartNew
withTaskCreationOptions.LongRunning
ne fait pas exactement la même chose - si j'avais besoin de la tâche pour utiliser l'LongRunning
option, ne pourrais-je pas utiliserTask.Run
comme vous l'avez montré, ou est-ce que je manque quelque chose?LongRunning
est un peu incompatible avec l'objectif de ne pas lier les threads. Si vous voulez garantir l' exécution sur son propre thread, vous pouvez l'utiliser, mais ici vous allez démarrer un thread qui est en veille la plupart du temps. Quel est le cas d'utilisation?LongRunning
utilisant laTask.Run
syntaxe. D'après la documentation, il semble que laTask.Run
syntaxe est plus propre, tant que vous êtes satisfait des paramètres par défaut qu'elle utilise. Il ne semble pas y avoir de surcharge qui prend unTaskCreationOptions
argument.Voici ce que j'ai trouvé:
NeverEndingTask
laExecutionCore
méthode et remplacez-la par le travail que vous souhaitez effectuer.ExecutionLoopDelayMs
vous permet d'ajuster le temps entre les boucles, par exemple si vous souhaitez utiliser un algorithme d'interruption.Start/Stop
fournir une interface synchrone pour démarrer / arrêter la tâche.LongRunning
signifie que vous obtiendrez un thread dédié parNeverEndingTask
.ActionBlock
solution basée ci-dessus.:
la source