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 async/attente. Plus tôt, j'avais utilisé BackgroundWorker pour gérer des processus plus longs en arrière-plan avec L'UI responsive.

ma question Est la suivante: après avoir eu ces nouvelles fonctionnalités, Quand devrais-je utiliser async/await et quand un BackgroundWorker ? Quels sont les scénarios communs pour les deux?

137
demandé sur Gennady Vanin Геннадий Ванин 2012-09-14 00:51:48

4 réponses

async / wait est conçu pour remplacer des constructions telles que le BackgroundWorker . Bien que vous certainement peut l'utiliser si vous le souhaitez, vous devriez être en mesure d'utiliser async/wait, avec quelques autres outils TPL, pour gérer tout ce qui est là-bas.

puisque les deux fonctionnent, cela revient à 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 à comprendre pour vous ?

65
répondu Servy 2012-09-13 21:08:08

c'est probablement TL;DR pour beaucoup, mais, je pense que comparer await avec BackgroundWorker c'est comme comparer des pommes et des oranges et mes pensées sur ce qui suit:

BackgroundWorker est destiné à modéliser une seule tâche que vous souhaitez effectuer en arrière-plan, sur un thread pool de threads. async / await est une syntaxe pour l'attente asynchrone sur des opérations asynchrones. Ces opérations peuvent utiliser ou non un filetage en pool ou même utiliser tout autre fil . 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 que dans un travailleur de fond, 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);

remarquez le manque de cohérence de l'élimination 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 habituellement pour modéliser une seule opération de longue durée que vous ne voulez pas affecter la réactivité de L'UI. 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 là-bas que vous pouvez 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 thread pour vous (en supposant la valeur par défaut TaskScheduler ), 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 est de savoir si vous signalez des progrès ou non. Par exemple, vous pourriez avoir un BackgroundWorker like :

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 feriez pas face à une partie de cela parce que vous traîneriez-et-déposer le composant de travailleur d'arrière-plan la surface de conception d'une forme--quelque chose que vous ne pouvez pas faire avec async / await et Task ... c'est à dire que vous n'aurez pas créer manuellement l'objet, définissez les propriétés et définir les gestionnaires d'événements. vous ne rempliriez que le corps des DoWork , RunWorkerCompleted , et ProgressChanged .

si vous "convertissiez" cela en async / attendre, 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 glisser un composant sur un concepteur surface, c'est au lecteur de décider ce qui est "meilleur". Mais, cela, pour moi , est la comparaison entre await et BackgroundWorker , pas si vous pouvez attendre des méthodes intégrées comme Stream.ReadAsync . par exemple, si vous utilisez 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

174
répondu Peter Ritchie 2016-08-25 15:15:32

c'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 Async

sont des opérations non bloquantes. Une expression d'attente dans une méthode asynchrone ne bloque pas le thread courant pendant que la tâche attendue est en cours d'exécution. Au lieu de cela, l'expression signe le reste de la méthode comme une continuation et renvoie le contrôle à l'appelant de la méthode asynchrone.

les mots-clés async et attente ne provoquent pas la création de threads supplémentaires. Les méthodes Async n'ont pas besoin de multithreading parce qu'une méthode async ne fonctionne pas sur son propre thread. La méthode s'exécute sur le contexte de synchronisation courant et utilise le temps sur le thread uniquement lorsque la méthode est active. Vous pouvez utiliser la tâche.Exécuter pour déplacer le travail lié au CPU 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 deviennent disponible.

async approche de la programmation asynchrone est préférable d'approches existantes dans presque tous les cas. En particulier, cette approche est meilleure que BackgroundWorker pour les opérations liées aux IO car le code est plus simple et vous n'avez pas à vous protéger contre les conditions de course. En combinaison avec la Tâche.Exécuter, la programmation async est meilleure que BackgroundWorker pour les opérations liées au CPU parce que la programmation async sépare les détails de coordination de l'exécution votre code du travail cette tâche.Effectuez les transferts vers le threadpool.

19
répondu TommyN 2012-09-13 21:30:15

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

article MSDN "la Programmation Asynchrone avec Async et Await (C# et Visual Basic)" dit:

async approche basée sur la programmation asynchrone est préférable aux approches existantes dans presque tous les cas . En particulier, cette l'approche est meilleure que BackgroundWorker 1519200920 "for IO-bound operations parce que le code est plus simple et vous ne pas se prémunir contre de course condition. En combinaison avec la Tâche.La programmation asynchrone est meilleure 151920920 1519200920" pour les opérations liées au CPU parce que async la programmation sépare les détails de coordination de l'exécution de votre code du travail que tâche.Exécuter transferts vers le threadpool

mise à JOUR

  • réponse au commentaire de @eran-otzap

    "pour les opérations liées aux IO parce que le code est plus simple et que vous n'avez pas à vous 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 de la il est le multithreading et à partir du même article MSDN Programmation Asynchrone avec Async et Await (C# et Visual Basic) :

Les méthodes Async

sont des opérations non bloquantes. Une attendent l'expression dans une méthode asynchrone ne bloque pas le thread la tâche attendue est en cours. Au lieu de cela, l'expression signe le reste de la méthode comme une continuation et retourne le contrôle à l'appelant de la méthode asynchrone.

les mots-clés async et en attente ne causent pas de threads supplémentaires à être créé. Les méthodes Async ne nécessitent pas de lecture multiple car un async la méthode ne fonctionne pas sur son propre thread. La méthode fonctionne sur le courant contexte de synchronisation et l'utilise de temps sur le fil lors de la la méthode est active. Vous pouvez utiliser la tâche.Exécuter pour déplacer le travail lié au CPU vers un thread d'arrière-plan, mais un thread d'arrière-plan n'aide pas avec un processus c'est juste attendre que les résultats deviennent disponible.

async approche de la programmation asynchrone est préférable de les approches existantes dans presque tous les cas. En particulier, cette approche est meilleur que le travailleur de fond pour les opérations liées à IO parce que le le code est plus simple et vous n'avez pas à vous protéger contre les conditions de course. En combinaison avec la Tâche.Exécuter, de programmation asynchrone est mieux que BackgroundWorker pour le CPU opérations en raison de programmation asynchrone sépare les détails de la coordination d'exécuter votre code à partir du travail cette Tâche.Exécuter les transferts vers le threadpool

C'est-à-dire,"les mots-clés async et attente ne provoquent pas la création de threads supplémentaires".

pour autant que je me souvienne de mes propres tentatives quand j'ai étudié cet article il y a un an, si vous avez couru et joué avec le code échantillon du même article, vous pourriez basculer dans la situation que ses versions non-asynchrones (vous pourriez essayer de le convertir à vous-même) bloquent indéfiniment!

aussi, pour des exemples concrets, vous pouvez rechercher ce site. Voici un exemple:

6
répondu Gennady Vanin Геннадий Ванин 2017-05-23 11:33:26