Async / Wait vs BackgroundWorker

162

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

J'aime ses nouvelles fonctionnalités async / await. 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: après avoir ces nouvelles fonctionnalités intéressantes, quand dois-je utiliser async / await et quand un BackgroundWorker ? Quels sont les scénarios communs pour les deux?

À M
la source
Les deux sont bons, mais si vous travaillez avec un code plus ancien qui n'a pas été migré vers une version ultérieure .net; BackgroundWorker fonctionne sur les deux.
dcarl661

Réponses:

74

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

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

Servy
la source
17
Merci. Pour moi, async / await semble beaucoup plus clair et «naturel». BakcgoundWorker rend le code «bruyant» à mon avis.
Tom
12
@Tom Eh bien, c'est pourquoi Microsoft a consacré beaucoup de temps et d'efforts à sa mise en œuvre. Si ce n'était pas mieux, ils n'auraient pas dérangé
Servy
5
Oui. Les nouveaux éléments d'attente donnent l'impression que l'ancien BackgroundWorker est totalement inférieur et obsolète. La différence est si dramatique.
usr
16
J'ai un assez bon aperçu sur mon blog comparant différentes approches des tâches en arrière-plan. Notez que async/ awaitpermet également la programmation asynchrone sans threads de pool de threads.
Stephen Cleary
8
A voté contre cette réponse, c'est trompeur. Async / await n'est PAS conçu pour remplacer le travail en arrière-plan.
Quango
206

C'est probablement TL; DR pour beaucoup, mais je pense que comparer awaitavec, BackgroundWorkerc'est comme comparer des pommes et des oranges et mes pensées à ce sujet sont les suivantes:

BackgroundWorkerest destiné à modéliser une tâche unique que vous souhaitez effectuer en arrière-plan, sur un thread de pool de threads. async/ awaitest une syntaxe pour l'attente asynchrone sur les opérations asynchrones. Ces opérations peuvent ou non utiliser un thread de pool de threads ou même utiliser n'importe quel 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 ça 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);

Remarquez la disjonction de l'élimination par rapport aux deux syntaxes et comment vous ne pouvez pas utiliser usingsans async/ await.

Mais vous ne feriez pas quelque chose comme ça avec BackgroundWorker. BackgroundWorkerest généralement destiné à modéliser une seule opération de longue durée dont 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 avec lequel vous pouvez utiliser async / await, qui BackgroundWorkercrée le thread 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 TaskSchedulercrée le thread pour vous (en supposant la valeur par défaut TaskScheduler), et pourrait utiliser awaitcomme 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 est de savoir si vous faites état de progrès ou non. Par exemple, vous pourriez avoir un BackgroundWorker likececi:

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();

Mais, vous ne traiteriez pas une partie de cela parce que vous feriez glisser-déposer le composant de travail d'arrière-plan sur l'aire de conception d'un formulaire - quelque chose que vous ne pouvez pas faire avec async/ awaitet Task... c'est-à-dire que vous avez gagné ' t créer manuellement l'objet, définir les propriétés et définir les gestionnaires d'événements. vous souhaitez remplir uniquement le corps des DoWork, RunWorkerCompletedet des ProgressChangedgestionnaires d'événements.

Si vous "convertissez" cela en async / await, 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 Designer, c'est vraiment au lecteur de décider lequel est «meilleur». Mais, pour moi, c'est la comparaison entre awaitet BackgroundWorker, pas si vous pouvez attendre des méthodes intégrées comme Stream.ReadAsync. par exemple, si vous utilisiez BackgroundWorkercomme prévu, il pourrait être difficile de le convertir await.

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

Peter Ritchie
la source
2
Une faille que je pense existe avec async / await est que vous souhaiterez peut-être démarrer plusieurs tâches asynchrones à la fois. Wait est censé attendre la fin de chaque tâche avant de commencer la suivante. Et si vous omettez le mot-clé await, la méthode s'exécute de manière synchrone, ce qui n'est pas ce que vous souhaitez. Je ne pense pas qu'async / await puisse résoudre un problème tel que "démarrer ces 5 tâches et me rappeler lorsque chaque tâche est effectuée sans ordre particulier".
Trevor Elliott
4
@Moozhe. Ce n'est pas vrai, vous pouvez le faire var t1 = webReq.GetResponseAsync(); var t2 = webReq2.GetResponseAsync(); await t1; await t2;. Ce qui attendrait deux opérations parallèles. Attendent est beaucoup mieux pour asynchrone, mais des tâches séquentielles, OMI ...
Peter Ritchie
2
@Moozhe oui, le faire de cette façon maintient une certaine séquence - comme je l'ai mentionné. c'est le point principal de await est d'obtenir l'asynchronisme dans un code de recherche séquentielle. Vous pouvez, bien sûr, utiliser await Task.WhenAny(t1, t2)pour faire quelque chose lorsque l'une ou l'autre tâche est terminée en premier. Vous voudrez probablement une boucle pour vous assurer que l'autre tâche se termine également. Habituellement, vous voulez savoir quand une tâche spécifique se termine, ce qui vous amène à écrire des séquences séquentielles await.
Peter Ritchie
2
N'est-ce pas .NET 4.0 TPL qui a rendu obsolètes les modèles asynchrones APM, EAP et BackgroundWorker? Toujours confus à ce sujet
Gennady Vanin Геннадий Ванин
5
Honnêteté, BackgroundWorker n'a jamais été bon pour les opérations liées aux E / S.
Peter Ritchie
21

Voici 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 destinées à être des opérations non bloquantes. Une expression d'attente dans une méthode async ne bloque pas le thread actuel pendant l'exécution de la tâche attendue. Au lieu de cela, l'expression signe le reste de la méthode en tant que continuation et renvoie le contrôle à l'appelant de la méthode async.

Les mots-clés async et await ne provoquent pas la création de threads supplémentaires. Les méthodes async ne nécessitent pas de multithreading car une méthode async 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é au processeur vers un thread d'arrière-plan, mais un thread d'arrière-plan n'aide pas avec 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 aux E / S 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 meilleure que BackgroundWorker pour les opérations liées au processeur, car la programmation async 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.

TommyN
la source
"pour les opérations liées aux E / S car le code est plus simple et vous n'avez pas à vous prémunir contre les conditions de concurrence" Quelles conditions de concurrence peuvent se produire, pourriez-vous donner un exemple?
eran otzap
8

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

L'article MSDN «Programmation asynchrone avec Async et Await (C # et Visual Basic)» indique:

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 aux E / S 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 meilleure que BackgroundWorker pour les opérations liées au processeur, car la programmation async sépare les détails de coordination de l'exécution de votre code du travail que Task.Run transfère vers le pool de threads

METTRE À JOUR

  • en réponse au commentaire de @ eran-otzap :
    "pour les opérations liées aux E / S car le code est plus simple et vous n'avez pas à vous prémunir contre les conditions de concurrence " Quelles conditions de concurrence peuvent se produire, pourriez-vous donner un exemple? "

Cette question aurait dû être posée dans un message distinct.

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

Les méthodes asynchrones sont destinées à être des opérations non bloquantes. Une expression d'attente dans une méthode async ne bloque pas le thread actuel pendant l'exécution de la tâche attendue. Au lieu de cela, l'expression signe le reste de la méthode en tant que continuation et renvoie le contrôle à l'appelant de la méthode async.

Les mots-clés async et await ne provoquent pas la création de threads supplémentaires. Les méthodes async ne nécessitent pas de multithreading car une méthode async 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é au processeur vers un thread d'arrière-plan, mais un thread d'arrière-plan n'aide pas avec un processus qui attend juste 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 aux E / S 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 meilleure que BackgroundWorker pour les opérations liées au processeur, car la programmation async sépare les détails de coordination de l'exécution de votre code du travail que Task.Run transfère vers le pool de threads

Autrement dit, "Les mots clés async et await ne provoquent pas la création de threads supplémentaires".

Pour autant que je me souvienne de mes propres tentatives lorsque j'étudiais cet article il y a un an, si vous avez exécuté et joué avec un échantillon de code du même article, vous pourriez tomber dans la situation où ses versions non asynchrones (vous pouvez essayer de convertir à vous-même) bloquer indéfiniment!

Vous pouvez également rechercher des exemples concrets sur ce site. Voici quelques exemples:

Gennady Vanin Геннадий Ванин
la source
30
BackgrondWorker n'est pas explicitement étiqueté comme obsolète dans .NET 4.5. L'article MSDN dit simplement que les opérations liées aux E / S sont meilleures avec les méthodes asynchrones - l'utilisation de BackgroundWorker ne signifie pas que vous ne pouvez pas utiliser les méthodes asynchrones.
Peter Ritchie
@PeterRitchie, j'ai corrigé ma réponse. Pour moi, «les approches existantes sont obsolètes» est synonyme de «L'approche asynchrone de la programmation asynchrone est préférable aux approches existantes dans presque tous les cas»
Gennady Vanin Геннадий Ванин
7
Je suis en désaccord avec cette page MSDN. D'une part, vous ne faites pas plus de «coordination» avec BGW qu'avec Task. Et oui, BGW n'a jamais été destiné à effectuer directement des opérations d'E / S - il y avait toujours eu une meilleure façon de faire des E / S que dans BGW. L'autre réponse montre que BGW n'est pas plus complexe à utiliser que Task. Et si vous utilisez BGW correctement, il n'y a pas de conditions de concurrence.
Peter Ritchie
"pour les opérations liées aux E / S car le code est plus simple et vous n'avez pas à vous prémunir contre les conditions de concurrence" Quelles conditions de concurrence peuvent se produire, pourriez-vous donner un exemple?
eran otzap
11
Cette réponse est fausse. La programmation asynchrone peut trop facilement déclencher des blocages dans des programmes non triviaux. En comparaison, BackgroundWorker est simple et solide.
ZunTzu