Assíncrono / espera versus BackgroundWorker

162

Nos últimos dias, testei os novos recursos do .net 4.5 e c # 5.

Eu gosto de seus novos recursos assíncronos / aguardados. Anteriormente, eu havia usado o BackgroundWorker para lidar com processos mais longos em segundo plano com a interface do usuário responsiva.

Minha pergunta é: depois de ter esses novos recursos, quando devo usar async / waitit e quando um BackgroundWorker ? Quais são os cenários comuns para ambos?

Tom
fonte
Ambos são bons, mas se você estiver trabalhando com código antigo que não foi migrado para uma versão .net posterior; BackgroundWorker funciona em ambos.
dcarl661

Respostas:

74

assíncrono / espera é projetado para substituir construções como o BackgroundWorker. Embora você certamente possa usá-lo, se quiser, poderá usar async / waitit, juntamente com algumas outras ferramentas TPL, para lidar com tudo o que existe por aí.

Como os dois funcionam, tudo se resume à preferência pessoal sobre a qual você usa quando. O que é mais rápido para você ? O que é mais fácil para você entender?

Servy
fonte
17
Obrigado. Para mim, assíncrono / espera parece muito mais claro e "natural". BakcgoundWorker torna o código 'barulhento' na minha opinião.
Tom
12
@ Tom Bem, é por isso que a Microsoft gastou muito tempo e esforço implementando-o. Se ele não era melhor eles não se preocuparam
Servy
5
Sim. O novo material aguardar faz com que o antigo BackgroundWorker pareça totalmente inferior e obsoleto. A diferença é tão dramática.
usr
16
Eu tenho um resumo muito bom no meu blog comparando abordagens diferentes para tarefas em segundo plano. Observe que async/ awaittambém permite programação assíncrona sem threads do conjunto de threads.
Stephen Cleary
8
A resposta foi negativa, é enganosa. Async / waitit NÃO foi projetado para substituir o trabalhador em segundo plano.
Quango
206

É provável que TL; DR para muitos, mas acho que comparar awaitcom BackgroundWorkeré como comparar maçãs e laranjas e meus pensamentos sobre isso a seguir:

BackgroundWorkerdestina-se a modelar uma única tarefa que você deseja executar em segundo plano, em um thread do conjunto de threads. async/ awaitÉ uma sintaxe para de forma assíncrona aguardando em operações assíncronas. Essas operações podem ou não usar um encadeamento de conjunto de encadeamentos ou até mesmo usar outro encadeamento . Então, são maçãs e laranjas.

Por exemplo, você pode fazer algo como o seguinte com await:

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

Mas, você provavelmente nunca modelaria isso em um trabalhador em segundo plano, provavelmente faria algo assim no .NET 4.0 (antes 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);

Observe a disjunção do descarte comparada entre as duas sintaxes e como você não pode usar usingsem async/ await.

Mas você não faria algo assim com BackgroundWorker. BackgroundWorkergeralmente é para modelar uma única operação de longa duração que você não deseja afetar a capacidade de resposta da interface do usuário. Por exemplo:

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

Não há realmente nada lá com o qual você possa usar async / waitit BackgroundWorkeresteja criando o thread para você.

Agora, você pode usar o TPL:

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

Nesse caso, o TaskSchedulerestá criando o encadeamento para você (assumindo o padrão TaskScheduler) e pode ser usado da awaitseguinte maneira:

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"

Na minha opinião, uma grande comparação é se você está relatando progresso ou não. Por exemplo, você pode ter BackgroundWorker likeisso:

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

Mas você não lidaria com isso porque arrastaria e soltaria o componente de trabalho em segundo plano na superfície de design de um formulário - algo que não pode ser feito com async/ awaite Task... ou seja, você venceu ' t crie manualmente o objeto, defina as propriedades e os manipuladores de eventos. você só preencher o corpo dos DoWork, RunWorkerCompletede ProgressChangedmanipuladores de eventos.

Se você "converteu" isso em assíncrono / espera, faria algo como:

     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"

Sem a capacidade de arrastar um componente para uma superfície do Designer, cabe ao leitor decidir qual é "melhor". Mas, para mim, essa é a comparação entre awaite BackgroundWorker, não se você pode aguardar métodos internos como Stream.ReadAsync. por exemplo, se você estiver usando BackgroundWorkercomo pretendido, pode ser difícil converter para usar await.

Outras opiniões: http://jeremybytes.blogspot.ca/2012/05/backgroundworker-component-im-not-dead.html

Peter Ritchie
fonte
2
Uma falha que acho que existe com async / waitit é que você pode querer iniciar várias tarefas assíncronas ao mesmo tempo. waitit deve aguardar a conclusão de cada tarefa antes de iniciar a próxima. E se você omitir a palavra-chave wait, o método será executado de forma síncrona, que não é o que você deseja. Eu não acho que assíncrono / espera possa resolver um problema como "inicie essas 5 tarefas e me ligue de volta quando cada tarefa for realizada em nenhuma ordem específica".
Trevor Elliott
4
@Moozhe. Não é verdade, você pode fazer var t1 = webReq.GetResponseAsync(); var t2 = webReq2.GetResponseAsync(); await t1; await t2;. O que aguardaria duas operações paralelas. Await é muito melhor para assíncrona, mas tarefas seqüenciais, IMO ...
Peter Ritchie
2
@Moozhe sim, fazê-lo dessa maneira mantém uma certa sequência - como eu mencionei. esse é o ponto principal a aguardar é obter a assincronicidade no código de aparência seqüencial. Obviamente, você poderia await Task.WhenAny(t1, t2)fazer algo quando a tarefa fosse concluída primeiro. Você provavelmente desejaria um loop para garantir que a outra tarefa também seja concluída. Geralmente, você deseja saber quando uma tarefa específica é concluída, o que o leva a escrever awaits sequenciais .
Peter Ritchie
2
Não foi o .NET 4.0 TPL que tornou obsoletos os padrões assíncronos do APM, EAP e BackgroundWorker? Ainda confuso sobre isso
Gennady Vanin Геннадий Ванин
5
Honestidade, o BackgroundWorker nunca foi bom para operações vinculadas à IO.
Peter Ritchie
21

Esta é uma boa introdução: http://msdn.microsoft.com/en-us/library/hh191443.aspx A seção Threads é exatamente o que você está procurando:

Os métodos assíncronos devem ser operações sem bloqueio. Uma expressão de espera em um método assíncrono não bloqueia o encadeamento atual enquanto a tarefa aguardada está em execução. Em vez disso, a expressão inscreve o restante do método como uma continuação e retorna o controle para o chamador do método assíncrono.

As palavras-chave assíncronas e aguardam não fazem com que threads adicionais sejam criados. Os métodos assíncronos não requerem multithreading porque um método assíncrono não é executado em seu próprio encadeamento. O método é executado no contexto de sincronização atual e usa o tempo no encadeamento apenas quando o método está ativo. Você pode usar o Task.Run para mover o trabalho vinculado à CPU para um encadeamento em segundo plano, mas um encadeamento em segundo plano não ajuda em um processo que está apenas aguardando a disponibilização dos resultados.

A abordagem assíncrona da programação assíncrona é preferível às abordagens existentes em quase todos os casos. Em particular, essa abordagem é melhor que o BackgroundWorker para operações vinculadas à IO porque o código é mais simples e você não precisa se proteger contra as condições de corrida. Em combinação com o Task.Run, a programação assíncrona é melhor que o BackgroundWorker para operações ligadas à CPU porque a programação assíncrona separa os detalhes de coordenação da execução do seu código do trabalho que o Task.Run transfere para o conjunto de threads.

TommyN
fonte
"para operações vinculadas à IO porque o código é mais simples e você não precisa se proteger contra as condições da corrida" Que condições da corrida podem ocorrer, você poderia dar um exemplo?
eran otzap
8

BackgroundWorker é explicitamente rotulado como obsoleto no .NET 4.5:

O artigo do MSDN "Programação assíncrona com Async e Await (C # e Visual Basic)" informa:

A abordagem assíncrona da programação assíncrona é preferível às abordagens existentes em quase todos os casos . Em particular, essa abordagem é melhor que o BackgroundWorker para operações vinculadas à IO porque o código é mais simples e você não precisa se proteger contra as condições de corrida. Em combinação com o Task.Run, a programação assíncrona é melhor que o BackgroundWorker para operações vinculadas à CPU porque a programação assíncrona separa os detalhes de coordenação da execução do código do trabalho que o Task.Run transfere para o conjunto de threads

ATUALIZAR

  • em resposta ao comentário de @ eran-otzap :
    "para operações com IO porque o código é mais simples e você não precisa se proteger contra as condições da corrida" Que condições da corrida podem ocorrer, você poderia dar um exemplo? "

Esta pergunta deveria ter sido colocada como um post separado.

A Wikipedia tem uma boa explicação das condições de corrida . A parte necessária é multithreading e do mesmo artigo do MSDN Programação assíncrona com Async e Await (C # e Visual Basic) :

Os métodos assíncronos devem ser operações sem bloqueio. Uma expressão de espera em um método assíncrono não bloqueia o encadeamento atual enquanto a tarefa aguardada está em execução. Em vez disso, a expressão inscreve o restante do método como uma continuação e retorna o controle para o chamador do método assíncrono.

As palavras-chave assíncronas e aguardam não fazem com que threads adicionais sejam criados. Os métodos assíncronos não requerem multithreading porque um método assíncrono não é executado em seu próprio encadeamento. O método é executado no contexto de sincronização atual e usa o tempo no encadeamento apenas quando o método está ativo. Você pode usar o Task.Run para mover o trabalho vinculado à CPU para um encadeamento em segundo plano, mas um encadeamento em segundo plano não ajuda em um processo que está apenas aguardando a disponibilização dos resultados.

A abordagem assíncrona da programação assíncrona é preferível às abordagens existentes em quase todos os casos. Em particular, essa abordagem é melhor que o BackgroundWorker para operações vinculadas à IO porque o código é mais simples e você não precisa se proteger contra as condições de corrida. Em combinação com o Task.Run, a programação assíncrona é melhor que o BackgroundWorker para operações vinculadas à CPU porque a programação assíncrona separa os detalhes de coordenação da execução do código do trabalho que o Task.Run transfere para o conjunto de threads

Ou seja, "As palavras-chave assíncronas e aguardam não causam a criação de threads adicionais".

Tanto quanto me lembro de minhas próprias tentativas, quando eu estava estudando este artigo, há um ano, se você executou e brincou com um exemplo de código do mesmo artigo, poderá encontrar situações nas versões não assíncronas (você pode tentar converter bloqueie indefinidamente!

Além disso, para exemplos concretos, você pode pesquisar neste site. Aqui estão alguns exemplos:

Gennady Vanin Геннадий Ванин
fonte
30
O BackgrondWorker não está explicitamente rotulado como obsoleto no .NET 4.5. O artigo do MSDN apenas diz que as operações vinculadas à IO são melhores com métodos assíncronos - o uso do BackgroundWorker não significa que você não pode usar métodos assíncronos.
Peter Ritchie
@ PeterRitchie, corrigi minha resposta. Para mim, "as abordagens existentes são obsoletos" é sinônimo de "A abordagem assíncrona para programação assíncrona é preferível abordagens existentes em quase todos os casos"
Gennady Vanin Геннадий Ванин
7
Eu tenho problemas com essa página do MSDN. Por um lado, você não faz mais "coordenação" com o BGW do que com o Task. E, sim, o BGW nunca teve a intenção de executar diretamente as operações de IO - sempre houve uma maneira melhor de executar o IO do que no BGW. A outra resposta mostra que o BGW não é mais complexo de usar que o Task. E se você usar o BGW corretamente, não haverá condições de corrida.
Peter Ritchie
"para operações vinculadas à IO porque o código é mais simples e você não precisa se proteger contra as condições da corrida" Que condições da corrida podem ocorrer, você poderia dar um exemplo?
eran otzap
11
Esta resposta está errada. A programação assíncrona pode facilmente desencadear conflitos em programas não triviais. Em comparação, o BackgroundWorker é simples e sólido.
ZunTzu 27/10/2015