Async / waitit contro BackgroundWorker


162

Negli ultimi giorni ho testato le nuove funzionalità di .net 4.5 ec # 5.

Mi piacciono le sue nuove funzionalità asincrone / attendono. In precedenza avevo usato BackgroundWorker per gestire processi più lunghi in background con un'interfaccia utente reattiva.

La mia domanda è: dopo avere queste nuove e belle funzionalità, quando dovrei usare async / wait e quando un BackgroundWorker ? Quali sono gli scenari comuni per entrambi?



Entrambi sono buoni, ma se si lavora con codice precedente che non è stato migrato a una versione .net successiva; BackgroundWorker funziona su entrambi.
dcarl661,

Risposte:


74

async / await è progettato per sostituire costrutti come BackgroundWorker. Mentre è certamente possibile utilizzarlo se si vuole, si dovrebbe essere in grado di utilizzare async / attendono, insieme a pochi altri strumenti TPL, a tutto ciò maniglia che c'è là fuori.

Dal momento che entrambi funzionano, si tratta di preferenze personali su cui si utilizza quando. Cosa è più veloce per te ? Cosa è più facile per te capire?


17
Grazie. Per me async / wait sembra molto più chiaro e "naturale". BakcgoundWorker rende il codice "rumoroso" secondo me.
Tom,

12
@ Tom: ecco perché Microsoft ha impiegato molto tempo e molti sforzi per implementarlo. Se non fosse stato meglio non si sarebbero preoccupati
Servy,

5
Sì. Le novità attendono che il vecchio BackgroundWorker appaia totalmente inferiore e obsoleto. La differenza è drammatica.
usr,

16
Ho una carrellata abbastanza buona sul mio blog che confronta diversi approcci con le attività in background. Si noti che async/ awaitconsente anche la programmazione asincrona senza thread del pool di thread.
Stephen Cleary,

8
Sottovalutato questa risposta, è fuorviante. Async / await NON è progettato per sostituire il lavoratore in background.
Quango,

206

Questo è probabilmente TL; DR per molti, ma, penso che confrontarsi awaitcon BackgroundWorkerè come confrontare mele e arance e i miei pensieri su questo seguono:

BackgroundWorkerè pensato per modellare una singola attività che vorresti eseguire in background, su un thread del pool di thread. async/ awaitè una sintassi per l'attesa asincrona delle operazioni asincrone. Tali operazioni possono o meno utilizzare un thread del pool di thread o addirittura utilizzare qualsiasi altro thread . Quindi, sono mele e arance.

Ad esempio, puoi fare qualcosa come il seguente con await:

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

Ma probabilmente non lo modelleresti mai in un lavoratore in background, potresti fare qualcosa del genere in .NET 4.0 (prima di 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);

Notare la disgiunzione della disposizione rispetto alle due sintassi e come non è possibile utilizzare usingsenza async/ await.

Ma non faresti qualcosa del genere con BackgroundWorker. BackgroundWorkerè in genere per la modellazione di un'unica operazione di lunga durata che non si desidera influire sulla reattività dell'interfaccia utente. Per esempio:

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

Non c'è davvero niente lì con cui puoi usare asincronizzazione / attesa, BackgroundWorkerè creare il thread per te.

Ora puoi usare TPL invece:

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

Nel qual caso TaskSchedulersta creando il thread per te (assumendo il valore predefinito TaskScheduler) e potrebbe usare awaitcome segue:

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"

Secondo me, un confronto importante è se stai segnalando progressi o meno. Ad esempio, potresti avere BackgroundWorker likequesto:

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

Ma non ti occuperesti di questo perché trascinerai il componente di lavoro in background nell'area di progettazione di un modulo - qualcosa che non puoi fare con async/ awaite Task... cioè che hai vinto ' t creare manualmente l'oggetto, impostare le proprietà e impostare i gestori di eventi. devi compilare solo nel corpo dei DoWork, RunWorkerCompletede ProgressChangedgestori di eventi.

Se lo "convertissi" in asincrono / attendi, faresti qualcosa del tipo:

     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"

Senza la possibilità di trascinare un componente su una superficie di Designer, spetta al lettore decidere quale sia "migliore". Ma questo, per me, è il confronto tra awaite BackgroundWorker, non se puoi aspettare metodi integrati come Stream.ReadAsync. ad esempio se si utilizzava BackgroundWorkercome previsto, potrebbe essere difficile convertirlo in uso await.

Altre riflessioni: http://jeremybytes.blogspot.ca/2012/05/backgroundworker-component-im-not-dead.html


2
Un difetto che penso esista con async / await è che potresti voler avviare più attività asincrone contemporaneamente. waitit è destinato ad attendere il completamento di ogni attività prima di iniziare la successiva. E se si omette la parola chiave waitit, il metodo viene eseguito in modo sincrono che non è quello che si desidera. Non credo che async / waitit possa risolvere un problema come "avviare queste 5 attività e richiamarmi quando ciascuna attività viene eseguita in un ordine particolare".
Trevor Elliott,

4
@Moozhe. Non è vero, puoi farlo var t1 = webReq.GetResponseAsync(); var t2 = webReq2.GetResponseAsync(); await t1; await t2;. Che aspetterebbe due operazioni parallele. Attendere è molto meglio per compiti asincroni, ma sequenziali, IMO ...
Peter Ritchie,

2
@Moozhe sì, farlo in questo modo mantiene una certa sequenza - come ho già detto. questo è il punto principale di attesa è ottenere l'asincronicità nel codice dall'aspetto sequenziale. Ovviamente potresti usare await Task.WhenAny(t1, t2)qualcosa quando una delle due attività è stata completata per prima. Probabilmente vorrai un ciclo per assicurarti che anche l'altra attività venga completata. Di solito vuoi sapere quando viene completata un'attività specifica , il che ti porta a scrivere sequenziali await.
Peter Ritchie,


5
Onestà, BackgroundWorker non è mai stato buono per le operazioni legate a IO.
Peter Ritchie,

21

Questa è una buona introduzione: http://msdn.microsoft.com/en-us/library/hh191443.aspx La sezione Discussioni è proprio quello che stai cercando:

I metodi asincroni sono intesi come operazioni non bloccanti. Un'espressione waitit in un metodo asincrono non blocca il thread corrente mentre il task atteso è in esecuzione. Al contrario, l'espressione registra il resto del metodo come continuazione e restituisce il controllo al chiamante del metodo asincrono.

Le parole chiave asincrone e attendono non causano la creazione di thread aggiuntivi. I metodi asincroni non richiedono il multithreading perché un metodo asincrono non viene eseguito sul proprio thread. Il metodo viene eseguito nel contesto di sincronizzazione corrente e utilizza l'ora sul thread solo quando il metodo è attivo. È possibile utilizzare Task.Run per spostare il lavoro associato alla CPU in un thread in background, ma un thread in background non aiuta con un processo che sta solo aspettando che i risultati diventino disponibili.

L'approccio asincrono alla programmazione asincrona è preferibile agli approcci esistenti in quasi tutti i casi. In particolare, questo approccio è migliore di BackgroundWorker per le operazioni associate a IO perché il codice è più semplice e non è necessario proteggersi dalle condizioni di gara. In combinazione con Task.Run, la programmazione asincrona è migliore di BackgroundWorker per le operazioni associate alla CPU poiché la programmazione asincrona separa i dettagli di coordinamento dell'esecuzione del codice dal lavoro che Task.Run trasferisce al thread pool.


"per operazioni legate a IO perché il codice è più semplice e non è necessario proteggersi dalle condizioni di gara" Quali condizioni di gara possono verificarsi, potresti fare un esempio?
Eran Otzap,

8

BackgroundWorker è esplicitamente etichettato come obsoleto in .NET 4.5:

L'articolo di MSDN "Programmazione asincrona con Async e Await (C # e Visual Basic)" dice:

L'approccio asincrono alla programmazione asincrona è preferibile agli approcci esistenti in quasi tutti i casi . In particolare, questo approccio è migliore di BackgroundWorker per le operazioni associate a IO perché il codice è più semplice e non è necessario proteggersi dalle condizioni di gara. In combinazione con Task.Run, la programmazione asincrona è migliore di BackgroundWorker per le operazioni associate alla CPU perché la programmazione asincrona separa i dettagli di coordinamento dell'esecuzione del codice dal lavoro che Task.Run trasferisce al threadpool

AGGIORNARE

  • in risposta al commento di @eran-otzap :
    "per le operazioni legate a IO perché il codice è più semplice e non è necessario proteggersi dalle condizioni di gara" Quali condizioni di gara possono verificarsi, potresti fare un esempio? "

Questa domanda avrebbe dovuto essere posta come un post separato.

Wikipedia ha una buona spiegazione delle condizioni di gara . La parte necessaria è il multithreading e dallo stesso articolo MSDN Programmazione asincrona con Async e Await (C # e Visual Basic) :

I metodi asincroni sono intesi come operazioni non bloccanti. Un'espressione waitit in un metodo asincrono non blocca il thread corrente mentre il task atteso è in esecuzione. Al contrario, l'espressione registra il resto del metodo come continuazione e restituisce il controllo al chiamante del metodo asincrono.

Le parole chiave asincrone e attendono non causano la creazione di thread aggiuntivi. I metodi asincroni non richiedono il multithreading perché un metodo asincrono non viene eseguito sul proprio thread. Il metodo viene eseguito nel contesto di sincronizzazione corrente e utilizza l'ora sul thread solo quando il metodo è attivo. È possibile utilizzare Task.Run per spostare il lavoro associato alla CPU in un thread in background, ma un thread in background non aiuta con un processo che sta solo aspettando che i risultati diventino disponibili.

L'approccio asincrono alla programmazione asincrona è preferibile agli approcci esistenti in quasi tutti i casi. In particolare, questo approccio è migliore di BackgroundWorker per le operazioni associate a IO perché il codice è più semplice e non è necessario proteggersi dalle condizioni di gara. In combinazione con Task.Run, la programmazione asincrona è migliore di BackgroundWorker per le operazioni associate alla CPU perché la programmazione asincrona separa i dettagli di coordinamento dell'esecuzione del codice dal lavoro che Task.Run trasferisce al threadpool

Cioè, "Le parole chiave asincrone e attendono non causano la creazione di thread aggiuntivi".

Per quanto possa ricordare i miei tentativi quando stavo studiando questo articolo un anno fa, se hai eseguito e giocato con il codice di esempio dallo stesso articolo, potresti imbatterti nella situazione che le sue versioni non asincrone (potresti provare a convertire a te stesso) bloccare indefinitamente!

Inoltre, per esempi concreti è possibile effettuare ricerche in questo sito. Ecco alcuni esempi:


30
BackgrondWorker non è esplicitamente etichettato come obsoleto in .NET 4.5. L'articolo MSDN afferma semplicemente che le operazioni associate a IO sono migliori con i metodi asincroni: l'uso di BackgroundWorker non significa che non è possibile utilizzare i metodi asincroni.
Peter Ritchie,

@ PeterRitchie, ho corretto la mia risposta. Per me "gli approcci esistenti sono obsoleti" è sinonimo di "L'approccio asincrono alla programmazione asincrona è preferibile agli approcci esistenti in quasi tutti i casi"
Gennady Vanin Геннадий Ванин

7
Prendo problemi con quella pagina MSDN. Per uno, non fai più "coordinamento" con BGW di quanto non faccia con Task. E sì, BGW non era mai stato progettato per eseguire direttamente le operazioni di IO - c'era sempre un modo migliore di fare IO che in BGW. L'altra risposta mostra che BGW non è più complesso da usare di Task. E se usi correttamente BGW, non ci sono condizioni di gara.
Peter Ritchie,

"per operazioni legate a IO perché il codice è più semplice e non è necessario proteggersi dalle condizioni di gara" Quali condizioni di gara possono verificarsi, potresti fare un esempio?
Eran Otzap,

11
Questa risposta è sbagliata La programmazione asincrona può attivare troppo facilmente deadlock in programmi non banali. In confronto BackgroundWorker è semplice e solido.
ZunTzu,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.