BackgroundWorker vs segmento de fundo

Eu tenho uma pergunta estilística sobre a escolha da implementação de thread em segundo plano que eu deveria usar em um aplicativo de formulário do windows. Atualmente eu tenho um BackgroundWorker em um formulário que tem um loop infinito (while(true)) . Neste loop eu uso WaitHandle.WaitAny para manter a thread cochilando até que algo de interesse aconteça. Um dos manipuladores de events que aguardo é um evento ” StopThread ” para que eu possa sair do loop. Esse evento é sinalizado quando do meu Form.Dispose() substituído Form.Dispose() .

Eu li em algum lugar que o BackgroundWorker é realmente destinado a operações que você não quer amarrar a interface do usuário e ter um fim finito – como baixar um arquivo ou processar uma seqüência de itens. Neste caso, o “fim” é desconhecido e somente quando a janela é fechada. Portanto, seria mais apropriado para mim usar um thread de BackgroundWorker de BackgroundWorker em vez de BackgroundWorker para essa finalidade?

Do meu entendimento da sua pergunta, você está usando um BackgroundWorker como um Thread padrão.

A razão pela qual BackgroundWorker é recomendado para coisas que você não quer amarrar o thread da interface do usuário é porque expõe alguns events agradáveis ​​ao fazer o desenvolvimento do Win Forms.

Eventos como RunWorkerCompleted para sinalizar quando o encadeamento concluiu o que ele precisava fazer e o evento ProgressChanged para atualizar a GUI no andamento dos encadeamentos.

Então, se você não estiver fazendo uso deles, não vejo mal algum em usar um Thread padrão para o que você precisa fazer.

Alguns dos meus pensamentos …

  1. Use BackgroundWorker se você tiver uma única tarefa executada em segundo plano e precisar interagir com a interface do usuário. A tarefa de organizar dados e chamadas de método para o thread da interface do usuário é tratada automaticamente por meio de seu modelo baseado em evento. Evite BackgroundWorker se …
    • sua assembly não tem ou não interage diretamente com a interface do usuário,
    • você precisa que o segmento seja um segmento em primeiro plano ou
    • você precisa manipular a prioridade do thread.
  2. Use um thread ThreadPool quando a eficiência é desejada. O ThreadPool ajuda a evitar a sobrecarga associada à criação, início e interrupção de encadeamentos. Evite usar o ThreadPool se …
    • a tarefa é executada durante a vida útil do seu aplicativo,
    • você precisa que o segmento seja um segmento em primeiro plano,
    • você precisa manipular a prioridade do encadeamento ou
    • você precisa que o thread tenha uma identidade fixa (abortando, suspendendo, descobrindo).
  3. Use a class Thread para tarefas de longa duração e quando você precisar de resources oferecidos por um modelo de threading formal, por exemplo, escolhendo entre threads em primeiro plano e em segundo plano, aprimorando a prioridade do thread, controle refinado sobre execução de thread, etc.

Praticamente o que Matt Davis disse, com os seguintes pontos adicionais:

Para mim, o principal diferenciador do BackgroundWorker é o empacotamento automático do evento concluído através do SynchronizationContext . Em um contexto de interface do usuário, isso significa que o evento concluído é acionado no thread da interface do usuário e, portanto, pode ser usado para atualizar a interface do usuário. Esse é um grande diferencial se você estiver usando o BackgroundWorker em um contexto de interface do usuário.

Tarefas executadas via ThreadPool não podem ser facilmente canceladas (isso inclui ThreadPool . QueueUserWorkItem e delegates são executados de forma assíncrona). Assim, enquanto isso evita a sobrecarga do spinup de threads, se você precisar de cancelamento, use um BackgroundWorker ou (mais provavelmente fora da UI) crie um thread e mantenha uma referência a ele para poder chamar Abort() .

Além disso, você está vinculando um thread de threadpool para a vida útil do worker de plano de fundo, o que pode ser uma preocupação, pois há apenas um número finito deles. Eu diria que, se você estiver criando o thread apenas uma vez para seu aplicativo (e não usar nenhum dos resources do worker em segundo plano), use um thread, em vez de um thread backgroundworker / threadpool.

Você sabe, às vezes é mais fácil trabalhar com um BackgroundWorker, independentemente de estar usando o Windows Forms, WPF ou qualquer outra tecnologia. A parte mais interessante sobre esses caras é que você começa a segmentar sem ter que se preocupar muito com o local onde o thread está sendo executado, o que é ótimo para tarefas simples.

Antes de usar um BackgroundWorker considere primeiro se você deseja cancelar um thread (fechamento do aplicativo, cancelamento do usuário), então você precisa decidir se o seu thread deve verificar se há cancelamentos ou se deve ser imposto à execução em si.

BackgroundWorker.CancelAsync() definirá CancellationPending como true mas não fará mais nada, então é responsabilidade do thread verificar continuamente isso, tenha em mente também que você pode acabar com uma condição de corrida nessa abordagem onde seu usuário cancelou, mas o encadeamento foi concluído antes do teste de CancellationPending .

Thread.Abort() outro lado, Thread.Abort() lançará uma exceção dentro da execução do thread que impõe o cancelamento desse thread, você deve ser cuidadoso com o que pode ser perigoso se essa exceção for levantada repentinamente na execução.

A segmentação precisa de uma consideração muito cuidadosa, não importa qual seja a tarefa, para alguma leitura adicional:

Programação Paralela nas Melhores Práticas de Encadeamento Gerenciado do .NET Framework

Eu sabia como usar threads antes de conhecer o .NET, então demorei para me acostumar quando comecei a usar o BackgroundWorkers. Matt Davis resumiu a diferença com grande excelência, mas eu acrescentaria que é mais difícil compreender exatamente o que o código está fazendo, e isso pode dificultar a debugging. É mais fácil pensar em criar e encerrar threads, IMO, do que pensar em dar trabalho a um pool de threads.

Eu ainda não posso comentar os posts de outras pessoas, então perdoe minha claudicação momentânea em usar uma resposta para abordar piers7

Não use Thread.Abort (); em vez disso, sinalize um evento e projete seu encadeamento para finalizar normalmente quando sinalizado. Thread.Abort () gera um ThreadAbortException em um ponto arbitrário na execução do thread, que pode fazer todos os tipos de coisas infelizes como monitores órfãos, estado compartilhado corrompido e assim por diante. http://msdn.microsoft.com/pt-br/library/system.threading.thread.abort.aspx

Se não está quebrado – conserte até que esteja … só brincando 🙂

Mas seriamente o BackgroundWorker é provavelmente muito parecido com o que você já tem, se você tivesse começado com ele desde o início, talvez você tivesse economizado algum tempo – mas neste momento eu não vejo a necessidade. A menos que algo não esteja funcionando, ou você acha que seu código atual é difícil de entender, então eu ficaria com o que você tem.

A diferença básica é, como você disse, gerar events GUI do BackgroundWorker . Se o encadeamento não precisar atualizar a exibição ou gerar events para o encadeamento GUI principal, ele poderá ser um encadeamento simples.

Um worker de plano de fundo é uma class que trabalha em um thread separado, mas fornece funcionalidade adicional que você não obtém com um Thread simples (como o gerenciamento de relatório de andamento da tarefa).

Se você não precisa dos resources adicionais dados por um trabalhador em segundo plano – e parece que não -, então um Thread seria mais apropriado.

Eu quero salientar um comportamento da class BackgroundWorker que não foi mencionado ainda. Você pode fazer um Thread normal para ser executado em segundo plano, definindo a propriedade Thread.IsBackground.

Encadeamentos em segundo plano são idênticos aos encadeamentos em primeiro plano, exceto que os encadeamentos em segundo plano não evitam que um processo seja finalizado. [ 1 ]

Você pode testar esse comportamento chamando o método a seguir no construtor da sua janela de formulário.

 void TestBackgroundThread() { var thread = new Thread((ThreadStart)delegate() { long count = 0; while (true) { count++; Debug.WriteLine("Thread loop count: " + count); } }); // Choose one option: thread.IsBackground = false; // <--- This will make the thread run in background thread.IsBackground = true; // <--- This will delay program termination thread.Start(); } 

Quando a propriedade IsBackground é configurada como true e você fecha a janela, seu aplicativo terminará normalmente.

Mas quando a propriedade IsBackground é definida como false (por padrão) e você fecha a janela, apenas a janela desaparecerá, mas o processo continuará funcionando.

A class BackgroundWorker utiliza um Thread que é executado em segundo plano.

O que me deixa perplexo é que o designer de estúdio visual só permite que você use BackgroundWorkers e Timers que realmente não funcionam com o projeto de serviço.

Ele oferece controles simples de arrastar e soltar para o seu serviço, mas … nem tente implantá-lo. Não vai funcionar.

Serviços: Somente use System.Timers.Timer System.Windows.Forms.Timer não funcionará, embora esteja disponível na checkbox de ferramentas

Serviços: BackgroundWorkers não funcionará quando estiver sendo executado como um serviço Use System.Threading.ThreadPools ou Chamadas assíncronas