web-dev-qa-db-fra.com

Async / wait vs BackgroundWorker

Ces derniers jours, j'ai testé les nouvelles fonctionnalités de .net 4.5 et c # 5.

J'aime ses nouvelles fonctionnalités asynchrones/wait. Auparavant, j'avais utilisé BackgroundWorker pour gérer des processus plus longs en arrière-plan avec une interface utilisateur réactive.

Ma question est la suivante: après avoir ces nouvelles fonctionnalités de Nice, quand devrais-je utiliser async/wait et quand BackgroundWorker ? Quels sont les scénarios courants pour les deux?

151
Tom

async/wait est conçu pour remplacer des constructions telles que BackgroundWorker. Bien que vous puissiez pouvez l'utiliser si vous le souhaitez, vous devriez pouvoir utiliser async/wait, avec quelques autres outils TPL, pour gérer tout ce qui existe.

Étant donné que les deux fonctionnent, cela dépend de la préférence personnelle que vous utilisez quand. Qu'est-ce qui est plus rapide pour vous? Qu'est-ce qui est plus facile pour vous à comprendre?

72
Servy

C'est probablement TL; DR pour beaucoup, mais je pense que comparer await avec BackgroundWorker revient à comparer des pommes et des oranges et voici ce que je pense de ceci:

BackgroundWorker est conçu pour modéliser une tâche unique que vous souhaitez effectuer en arrière-plan, sur un thread de pool de threads. async/await est une syntaxe pour l'attente asynchrone sur des opérations asynchrones. Ces opérations peuvent ou non utiliser un thread de pool de threads ou même utiliser tout autre thread . Donc, ce sont des pommes et des oranges.

Par exemple, vous pouvez faire quelque chose comme ce qui suit avec await:

using (WebResponse response = await webReq.GetResponseAsync())
{
    using (Stream responseStream = response.GetResponseStream())
    {
        int bytesRead = await responseStream.ReadAsync(buffer, 0, buffer.Length);
    }
}

Mais vous ne modéliseriez probablement jamais cela dans un travailleur d'arrière-plan, vous feriez probablement quelque chose comme ceci dans .NET 4.0 (avant await):

webReq.BeginGetResponse(ar =>
{
    WebResponse response = webReq.EndGetResponse(ar);
    Stream responseStream = response.GetResponseStream();
    responseStream.BeginRead(buffer, 0, buffer.Length, ar2 =>
    {
        int bytesRead = responseStream.EndRead(ar2);
        responseStream.Dispose();
        ((IDisposable) response).Dispose();
    }, null);
}, null);

Notez la discordance de la disposition comparée entre les deux syntaxes et comment vous ne pouvez pas utiliser using sans async/await.

Mais vous ne feriez pas quelque chose comme ça avec BackgroundWorker. BackgroundWorker est généralement utilisé pour modéliser une seule opération longue que vous ne souhaitez pas affecter la réactivité de l'interface utilisateur. Par exemple:

worker.DoWork += (sender, e) =>
                    {
                    int i = 0;
                    // simulate lengthy operation
                    Stopwatch sw = Stopwatch.StartNew();
                    while (sw.Elapsed.TotalSeconds < 1)
                        ++i;
                    };
worker.RunWorkerCompleted += (sender, eventArgs) =>
                                {
                                    // TODO: do something on the UI thread, like
                                    // update status or display "result"
                                };
worker.RunWorkerAsync();

Il n'y a vraiment rien que vous puissiez utiliser async/wait avec, BackgroundWorker crée le fil pour vous.

Maintenant, vous pouvez utiliser TPL à la place:

var synchronizationContext = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() =>
                      {
                        int i = 0;
                        // simulate lengthy operation
                        Stopwatch sw = Stopwatch.StartNew();
                        while (sw.Elapsed.TotalSeconds < 1)
                            ++i;
                      }).ContinueWith(t=>
                                      {
                                        // TODO: do something on the UI thread, like
                                        // update status or display "result"
                                      }, synchronizationContext);

Dans ce cas, le TaskScheduler crée le fil pour vous (en supposant que TaskScheduler par défaut), et pourrait utiliser await comme suit:

await Task.Factory.StartNew(() =>
                  {
                    int i = 0;
                    // simulate lengthy operation
                    Stopwatch sw = Stopwatch.StartNew();
                    while (sw.Elapsed.TotalSeconds < 1)
                        ++i;
                  });
// TODO: do something on the UI thread, like
// update status or display "result"

À mon avis, une comparaison majeure consiste à déterminer si vous signalez des progrès ou non. Par exemple, vous pourriez avoir un BackgroundWorker like cette:

BackgroundWorker worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.ProgressChanged += (sender, eventArgs) =>
                            {
                            // TODO: something with progress, like update progress bar

                            };
worker.DoWork += (sender, e) =>
                 {
                    int i = 0;
                    // simulate lengthy operation
                    Stopwatch sw = Stopwatch.StartNew();
                    while (sw.Elapsed.TotalSeconds < 1)
                    {
                        if ((sw.Elapsed.TotalMilliseconds%100) == 0)
                            ((BackgroundWorker)sender).ReportProgress((int) (1000 / sw.ElapsedMilliseconds));
                        ++i;
                    }
                 };
worker.RunWorkerCompleted += (sender, eventArgs) =>
                                {
                                    // do something on the UI thread, like
                                    // update status or display "result"
                                };
worker.RunWorkerAsync();

Cependant, vous ne traiteriez pas certains de ces problèmes, car vous glisser-déposez le composant de travail en arrière-plan sur la surface de dessin d'un formulaire, ce que vous ne pouvez pas faire avec async/await et Task... vous ne créerez pas d'objet manuellement, ne définissez pas les propriétés ni les gestionnaires d'événements. vous ne feriez que remplir le corps des gestionnaires d'événement DoWork, RunWorkerCompleted et ProgressChanged.

Si vous "convertissiez" cela en asynchrone/wait, vous feriez quelque chose comme:

     IProgress<int> progress = new Progress<int>();

     progress.ProgressChanged += ( s, e ) =>
        {
           // TODO: do something with e.ProgressPercentage
           // like update progress bar
        };

     await Task.Factory.StartNew(() =>
                  {
                    int i = 0;
                    // simulate lengthy operation
                    Stopwatch sw = Stopwatch.StartNew();
                    while (sw.Elapsed.TotalSeconds < 1)
                    {
                        if ((sw.Elapsed.TotalMilliseconds%100) == 0)
                        {
                            progress.Report((int) (1000 / sw.ElapsedMilliseconds))
                        }
                        ++i;
                    }
                  });
// TODO: do something on the UI thread, like
// update status or display "result"

Sans la possibilité de faire glisser un composant sur une surface de Designer, c'est vraiment au lecteur de décider lequel est le "meilleur". Mais, pour moi, c’est la comparaison entre await et BackgroundWorker, et non pas si vous pouvez attendre des méthodes intégrées telles que Stream.ReadAsync. par exemple. Si vous utilisiez BackgroundWorker comme prévu, il pourrait être difficile de convertir utiliser await.

Autres pensées: http://jeremybytes.blogspot.ca/2012/05/backgroundworker-component-im-not-dead.html

193
Peter Ritchie

Ceci est une bonne introduction: http://msdn.Microsoft.com/en-us/library/hh191443.aspx La section Threads est exactement ce que vous recherchez:

Les méthodes asynchrones sont conçues pour être des opérations non bloquantes. Une expression en attente dans une méthode asynchrone ne bloque pas le thread en cours pendant l'exécution de la tâche attendue. Au lieu de cela, l'expression inscrit le reste de la méthode en tant que continuation et redonne le contrôle à l'appelant de la méthode asynchrone.

Les mots-clés async et wait n'entraînent pas la création de threads supplémentaires. Les méthodes asynchrones ne nécessitent pas de multithreading car une méthode asynchrone ne s'exécute pas sur son propre thread. La méthode s'exécute sur le contexte de synchronisation actuel et utilise l'heure sur le thread uniquement lorsque la méthode est active. Vous pouvez utiliser Task.Run pour déplacer le travail lié à la CPU vers un fil d’arrière-plan, mais un fil d’arrière-plan ne vous aide pas dans un processus qui attend simplement que les résultats soient disponibles.

L'approche asynchrone de la programmation asynchrone est préférable aux approches existantes dans presque tous les cas. En particulier, cette approche est meilleure que BackgroundWorker pour les opérations liées à l'IO car le code est plus simple et vous n'avez pas à vous prémunir contre les conditions de concurrence. En combinaison avec Task.Run, la programmation asynchrone est préférable à BackgroundWorker pour les opérations liées au processeur, car la programmation asynchrone sépare les détails de la coordination de l'exécution de votre code du travail que Task.Run transfère au pool de threads.

20
TommyN

BackgroundWorker est explicitement étiqueté comme obsolète dans .NET 4.5:

L'article MSDN "Programmation asynchrone avec async et attente (C # et Visual Basic)" dit:

L'approche asynchrone de la programmation asynchrone est préférable aux approches existantes dans presque tous les cas . En particulier, cette approche est meilleure que BackgroundWorker pour les opérations liées à l'IO car le code est plus simple et vous n'avez pas se prémunir contre les conditions de course. En combinaison avec Task.Run, la programmation async est meilleure que BackgroundWorker pour les opérations liées à la CPU car la programmation async sépare les détails de la coordination. d'exécuter votre code à partir du travail que Task.Run transfère au pool de threads

[~ # ~] met à jour [~ # ~]

  • en réponse au commentaire de @ eran-otzap :
    "pour les opérations IO liées parce que le code est plus simple et qu’il n’est pas nécessaire de se prémunir contre les conditions de course" Quelles conditions de course peuvent se produire, pourriez-vous donner un exemple? "

Cette question aurait dû être posée séparément.

Wikipedia a une bonne explication de racing conditions. La partie nécessaire est multithreading et provient du même article MSDN Programmation asynchrone avec Async et Await (C # et Visual Basic) :

Les méthodes asynchrones sont conçues pour être des opérations non bloquantes. Une expression en attente dans une méthode asynchrone ne bloque pas le thread en cours pendant l'exécution de la tâche attendue. Au lieu de cela, l'expression inscrit le reste de la méthode en tant que continuation et redonne le contrôle à l'appelant de la méthode asynchrone.

Les mots-clés async et wait n'entraînent pas la création de threads supplémentaires. Les méthodes asynchrones ne nécessitent pas de multithreading car une méthode asynchrone ne s'exécute pas sur son propre thread. La méthode s'exécute sur le contexte de synchronisation actuel et utilise l'heure sur le thread uniquement lorsque la méthode est active. Vous pouvez utiliser Task.Run pour déplacer le travail lié à la CPU vers un fil d’arrière-plan, mais un fil d’arrière-plan ne vous aide pas dans un processus qui attend simplement que les résultats soient disponibles.

L'approche asynchrone de la programmation asynchrone est préférable aux approches existantes dans presque tous les cas. En particulier, cette approche est meilleure que BackgroundWorker pour les opérations liées à l'IO car le code est plus simple et vous n'avez pas à vous prémunir contre les conditions de concurrence. En combinaison avec Task.Run, la programmation asynchrone est préférable à BackgroundWorker pour les opérations liées au processeur car la programmation asynchrone sépare les détails de coordination de l'exécution de votre code du travail que Task.Run transfère au pool de threads.

Autrement dit, "les mots clés async et wait n'entraînent pas la création de threads supplémentaires".

Dans la mesure où je me souviens de mes propres tentatives lorsque j’étudiais cet article il ya un an, si vous aviez utilisé et joué avec un exemple de code du même article, vous pourriez tomber si ses versions non asynchrones (vous pouvez essayer de convertir à vous-même) bloquer indéfiniment!

Aussi, pour des exemples concrets, vous pouvez rechercher ce site. Voici quelques exemples: