Quelle est la bonne façon de combiner long tâches en cours d'exécution avec async / await modèle?
J'ai une "Haute Précision" classe timer que j'ai besoin pour être en mesure de démarrer, arrêter, & mettre en pause /reprendre. Pour ce faire, je suis le lien entre un couple de différents exemples que j'ai trouvé sur internet, mais je ne suis pas sûr si je suis à l'aide de Tâches avec asnyc /vous attendent correctement.
Voici mon code:
//based on http://haukcode.wordpress.com/2013/01/29/high-precision-timer-in-netc/
public class HighPrecisionTimer : IDisposable
{
Task _task;
CancellationTokenSource _cancelSource;
//based on http://blogs.msdn.com/b/pfxteam/archive/2013/01/13/cooperatively-pausing-async-methods.aspx
PauseTokenSource _pauseSource;
Stopwatch _watch;
Stopwatch Watch { get { return _watch ?? (_watch = Stopwatch.StartNew()); } }
public bool IsPaused
{
get { return _pauseSource != null && _pauseSource.IsPaused; }
private set
{
if (value)
{
_pauseSource = new PauseTokenSource();
}
else
{
_pauseSource.IsPaused = false;
}
}
}
public bool IsRunning { get { return !IsPaused && _task != null && _task.Status == TaskStatus.Running; } }
public void Start()
{
if (IsPaused)
{
IsPaused = false;
}
else if (!IsRunning)
{
_cancelSource = new CancellationTokenSource();
_task = new Task(ExecuteAsync, _cancelSource.Token, TaskCreationOptions.LongRunning);
_task.Start();
}
}
public void Stop()
{
if (_cancelSource != null)
{
_cancelSource.Cancel();
}
}
public void Pause()
{
if (!IsPaused)
{
if (_watch != null)
{
_watch.Stop();
}
}
IsPaused = !IsPaused;
}
async void ExecuteAsync()
{
while (!_cancelSource.IsCancellationRequested)
{
if (_pauseSource != null && _pauseSource.IsPaused)
{
await _pauseSource.Token.WaitWhilePausedAsync();
}
//DO CUSTOM TIMER STUFF...
}
if (_watch != null)
{
_watch.Stop();
_watch = null;
}
_cancelSource = null;
_pauseSource = null;
}
public void Dispose()
{
if (IsRunning)
{
_cancelSource.Cancel();
}
}
}
Quelqu'un peut veuillez jeter un oeil et me donner quelques conseils sur la façon de savoir si je suis en train de faire cela correctement?
Mise à JOUR
J'ai essayé de modifier mon code par Noseratio les commentaires ci-dessous, mais je ne peux toujours pas comprendre la syntaxe. Chaque tentative de passer la ExecuteAsync() méthode pour TaskFactory.StartNew ou Tâche.Exécuter, provoque une erreur de compilation comme suit:
"L'appel est ambigu entre les méthodes suivantes ou propriétés: TaskFactory.StartNew(Action, CancellationToken...) et TaskFactory.StartNew<Tâche>(Func<Tâche>, CancellationToken...)".
Enfin, est-il un moyen de spécifier le LongRunning TaskCreationOption sans avoir à fournir un TaskScheduler?
async **Task** ExecuteAsync()
{
while (!_cancelSource.IsCancellationRequested)
{
if (_pauseSource != null && _pauseSource.IsPaused)
{
await _pauseSource.Token.WaitWhilePausedAsync();
}
//...
}
}
public void Start()
{
//_task = Task.Factory.StartNew(ExecuteAsync, _cancelSource.Token, TaskCreationOptions.LongRunning, null);
//_task = Task.Factory.StartNew(ExecuteAsync, _cancelSource.Token);
//_task = Task.Run(ExecuteAsync, _cancelSource.Token);
}
Mise à JOUR 2
Je pense que j'ai trouvé, mais pas encore sûr de la syntaxe correcte. Serait-ce la bonne façon de créer de la tâche, de sorte que le consommateur /code d'appel se poursuit, avec la tâche de filature et de départ sur un nouveau thread asynchrone?
_task = Task.Run(async () => await ExecuteAsync, _cancelSource.Token);
//**OR**
_task = Task.Factory.StartNew(async () => await ExecuteAsync, _cancelSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
async () => await ExecuteAsync
lambda à Task.Factory.StartNew
ne résout pas le problème que j'ai décrit dans le #1. Essentiellement, ça change rien. Découvrez une mise à jour de ma réponse pour un exemple de la syntaxe correcte et logique.En fait, maintenant que vous avez changé le type de retour de
ExecuteAsync
à Task
, en passant async () => await ExecuteAsync
lambda à Task.Factory.StartNew
, mais c'est redondant. Il suffit de passer ExecuteAsync
et ne task.Unwrap
sur le Task<Task>
objet renvoyé par Task.Factory.StartNew
.OriginalL'auteur Joshua Barker | 2013-11-28
Vous devez vous connecter pour publier un commentaire.
Voici quelques points:
async void
méthodes sont seulement bon pour les gestionnaires d'événements asynchrones (plus d'infos). Votreasync void ExecuteAsync()
retourne instantanément (dès que le flux de code atteintawait _pauseSource
à l'intérieur). Essentiellement, votre_task
est dans l'état terminé après que, alors que le reste deExecuteAsync
sera exécuté inaperçu (parce que c'estvoid
). Il peut même ne pas poursuivre l'exécution à tous, tout dépend de votre thread principal (et donc, le processus se termine.Étant donné que, vous devriez le faire
async Task ExecuteAsync()
, et l'utilisationTask.Run
ouTask.Factory.StartNew
au lieu denew Task
pour le démarrer. Parce que vous voulez que votre tâche est de l'action de la méthode deasync
, vous seriez confronté à imbriquée tâches ici, c'est à direTask<Task>
, quiTask.Run
serait automatiquement déballer pour vous. Plus d'informations peuvent être trouvées ici et ici.PauseTokenSource
adopte l'approche suivante (de par leur conception, AFAIU): le consommateur côté du code (celui qui appellePause
) en fait ne demande une pause, mais ne synchronise pas sur elle. Il va continuer à exécuter aprèsPause
, même si le producteur ne peut avoir atteint l'attente de l'état et pourtant, c'est à direawait _pauseSource.Token.WaitWhilePausedAsync()
. Cela peut être correct pour votre application logique, mais vous devez être conscients de. Plus d'infos ici.[Mise à JOUR] ci-Dessous est la syntaxe correcte pour l'utilisation de
Factory.StartNew
. NoteTask<Task>
ettask.Unwrap
. Notez également_task.Wait()
dansStop
, il est là pour s'assurer que la tâche est terminée lorsqueStop
retours (d'une manière similaire àThread.Join
). Aussi,TaskScheduler.Default
est utilisé pour charger desFactory.StartNew
à utiliser le pool de threads planificateur. Ceci est important si votre créer votreHighPrecisionTimer
objet à partir de l'intérieur d'une autre tâche, qui à son tour a été créé sur un fil avec des non-synchronisation par défaut le contexte, par exemple, un thread d'INTERFACE utilisateur (plus d'info ici et ici).J'ai mis à jour ma réponse avec un peu de code montrant la syntaxe correcte pour
Task.Factory.StartNew
, le long avec un peu plus de thoughs. Tout devrait s'appliquer à Mono environnement d'exécution. Je crois qu'ils suivent l' .NET spécifications d'assez près.Donc j'ai fini par la création de deux variables, une pour Tâche<Tâche> et l'autre, juste de la Tâche. Note intéressante, mais quand j'examine à la fois des variables dans la fenêtre de débogage, de ses l ' "extérieur" de la Tâche wrapper qui a son statut "en cours", alors que l'intérieur de la Tâche est toujours "WaitingForActiviation"... Afin de l'appelant pause fonctionne maintenant, en ce que je peux mettre en pause / reprendre mon thread en cours d'exécution, mais quelque chose de bizarre dans iOS ou MonoTouch causes de ce thread pour se déplacer à partir d'un thread d'arrière-plan (#5) à l'INTERFACE principale de thread (#1)... Deux pas en avant, 1 pas en arrière...
Ok... merci pour la clarification. Il semble que cela peut être un bug dans Xamarin de la mise en œuvre des Tâches (voir forums.xamarin.com/discussion/10858/... et bugzilla.xamarin.com/show_bug.cgi?id=16548 pour plus de détails). J'espère qu'ils la résolution de ce problème dès que possible. En attendant, je vous remercie tous de votre aide!
_task.Wait()
dansStop()
. Qui m'a beaucoup aidé!OriginalL'auteur noseratio