web-dev-qa-db-fra.com

Événement de progression asynchrone / en attente C # sur l'objet Tâche <>

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;
31
Connell

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.

43
Jon Skeet

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:

  1. Par convention, le paramètre 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.
  2. Les rapports de progression sont eux-mêmes asynchrones, vous devez donc créer une nouvelle instance de vos arguments à chaque appel (encore mieux, utilisez simplement des types immuables pour vos arguments d'événement). Vous ne devez pas ne pas muter, puis réutiliser le même objet arguments pour plusieurs appels à Progress.
  3. Le 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.
48
Stephen Cleary

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 ™.

7
Chris Marisic