Je suis complètement nouveau dans les nouveaux mots clés async
/await
de C # 5 et je suis intéressé par la meilleure façon de mettre en œuvre un événement de progression.
Maintenant, je préférerais qu'un événement Progress
soit sur le Task<>
lui-même. Je sais que je pourrais simplement mettre l'événement dans la classe qui contient la méthode asynchrone et passer une sorte d'objet d'état dans le gestionnaire d'événements, mais cela me semble être plus une solution de contournement qu'une solution. Je peux également souhaiter que différentes tâches déclenchent des gestionnaires d'événements dans différents objets, ce qui semble désordonné de cette façon.
Existe-t-il un moyen de faire quelque chose de similaire à ce qui suit?:
var task = scanner.PerformScanAsync();
task.ProgressUpdate += scanner_ProgressUpdate;
return await task;
Autrement dit, Task
ne prend pas en charge la progression. Cependant, il existe déjà une méthode conventionnelle pour ce faire, en utilisant IProgress<T>
interface. Le modèle asynchrone basé sur les tâches suggère essentiellement de surcharger vos méthodes asynchrones (là où cela a du sens) pour permettre aux clients de passer un IProgess<T>
la mise en oeuvre. Votre méthode asynchrone rapporterait alors les progrès via cela.
L'API Windows Runtime (WinRT) possède des indicateurs de progression intégrés, dans le IAsyncOperationWithProgress<TResult, TProgress>
et IAsyncActionWithProgress<TProgress>
types ... donc si vous écrivez réellement pour WinRT, ceux-ci valent la peine d'être examinés - mais lisez également les commentaires ci-dessous.
L'approche recommandée est décrite dans la documentation Pattern asynchrone basé sur les tâches , qui donne à chaque méthode asynchrone sa propre IProgress<T>
:
public async Task PerformScanAsync(IProgress<MyScanProgress> progress)
{
...
if (progress != null)
progress.Report(new MyScanProgress(...));
}
Usage:
var progress = new Progress<MyScanProgress>();
progress.ProgressChanged += ...
PerformScanAsync(progress);
Remarques:
progress
peut être null
si l'appelant n'a pas besoin de rapports de progression, alors assurez-vous de vérifier cela dans votre méthode async
.Progress
.Progress<T>
type capturera le contexte actuel (par exemple, le contexte de l'interface utilisateur) lors de la construction et déclenchera son événement ProgressChanged
dans ce contexte. Vous n'avez donc pas à vous soucier du marshaling vers le thread d'interface utilisateur avant d'appeler Report
.J'ai dû reconstituer cette réponse à partir de plusieurs messages alors que j'essayais de comprendre comment faire fonctionner ce code moins trivial (c'est-à-dire que les événements notifient les changements).
Supposons que vous ayez un processeur d'articles synchrone qui annoncera le numéro d'article sur lequel il est sur le point de commencer à travailler. Pour mon exemple, je vais juste manipuler le contenu du bouton Processus, mais vous pouvez facilement mettre à jour une barre de progression, etc.
private async void BtnProcess_Click(object sender, RoutedEventArgs e)
{
BtnProcess.IsEnabled = false; //prevent successive clicks
var p = new Progress<int>();
p.ProgressChanged += (senderOfProgressChanged, nextItem) =>
{ BtnProcess.Content = "Processing page " + nextItem; };
var result = await Task.Run(() =>
{
var processor = new SynchronousProcessor();
processor.ItemProcessed += (senderOfItemProcessed , e1) =>
((IProgress<int>) p).Report(e1.NextItem);
var done = processor.WorkItWorkItRealGood();
return done ;
});
BtnProcess.IsEnabled = true;
BtnProcess.Content = "Process";
}
L'élément clé est de clôturer le Progress<>
variable à l'intérieur de l'abonnement ItemProcessed
. Cela permet à tout de Just works ™
.