BackgroundWorker vs fundo Thread

166

Tenho uma pergunta estilística sobre a escolha da implementação do encadeamento em segundo plano que devo usar em um aplicativo de formulário do Windows. Atualmente eu tenho um BackgroundWorkerformulário que possui um (while(true))loop infinito . Nesse loop, eu uso WaitHandle.WaitAnypara manter o fio cochilando até que algo de interesse aconteça. Um dos manipuladores de eventos que eu espero " StopThread" é um evento para que eu possa sair do loop. Este evento é sinalizado quando da minha substituição Form.Dispose().

Li em algum lugar que BackgroundWorkeré realmente destinado a operações com as quais você não deseja amarrar a interface do usuário e ter um fim finito - como baixar um arquivo ou processar uma sequência de itens. Nesse caso, o "fim" é desconhecido e somente quando a janela é fechada. Portanto, seria mais apropriado usar um Thread de segundo plano em vez de BackgroundWorkerpara esse fim?

freddy smith
fonte

Respostas:

88

Pelo que entendi da sua pergunta, você está usando um BackgroundWorkerThread como padrão.

O motivo pelo qual BackgroundWorkeré recomendado para coisas que você não deseja vincular ao thread da interface do usuário é porque ele expõe alguns eventos interessantes ao desenvolver o Win Forms.

Os eventos gostam RunWorkerCompletedde sinalizar quando o encadeamento concluiu o que precisava fazer e o ProgressChangedevento para atualizar a GUI no andamento dos encadeamentos.

Portanto, se você não estiver usando isso, não vejo mal em usar um Thread padrão para o que você precisa fazer.

ParmesanCodice
fonte
outra questão de que não tenho certeza é: suponha que esteja tentando dispor o formulário que contém o trabalhador em segundo plano. Eu sinalizo o evento shutdown (ManualResetEvent) e, algum tempo depois, o DoWork sairá normalmente. Devo apenas deixar o formulário seguir em frente e Dispose, mesmo que o DoWork possa demorar um pouco mais para terminar ou haja alguma maneira (e é melhor) de encadear. do formulário continua?
Freddy smith
Acho BackgroundWorker.IsBusy é o que você está procurando lá.
ParmesanCodice
1
Só uso CancelAsync(e teste CancellationPendingse o seu fio será polling em curtos intervalos de tempo, se você quer ter uma exceção gerada em vez disso, use um System.Threading.Thread.Abort()que levanta uma exceção dentro do próprio bloco de rosca, escolher o modelo certo para a situação.
Brett Ryan
369

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 as chamadas de dados e métodos para o encadeamento da interface do usuário é manipulada automaticamente por meio de seu modelo baseado em eventos. Evite o BackgroundWorker se ...
    • seu assembly não possui ou não interage diretamente com a interface do usuário,
    • você precisa que o encadeamento seja um encadeamento em primeiro plano ou
    • você precisa manipular a prioridade do encadeamento.
  2. Use um ThreadPool thread quando a eficiência for desejada. O ThreadPool ajuda a evitar a sobrecarga associada à criação, inicialização e parada de threads. Evite usar o ThreadPool se ...
    • a tarefa é executada durante toda a vida útil do seu aplicativo,
    • você precisa que o thread seja um primeiro plano,
    • você precisa manipular a prioridade do encadeamento ou
    • você precisa que o encadeamento tenha uma identidade fixa (abortando, suspendendo, descobrindo).
  3. Use a classe Thread para tarefas de longa execução e quando precisar de recursos oferecidos por um modelo formal de encadeamento, por exemplo, escolhendo entre segmentos de primeiro e segundo plano, aprimorando a prioridade do segmento, controle refinado sobre a execução do segmento, etc.
Matt Davis
fonte
10
O trabalhador em segundo plano está no assembly System.dll e no espaço para nome System.ComponentModel. Não há dependência no WinForms.
Kugel
17
Isso está correto, mas BackgroundWorkerfoi projetado para relatar o progresso do encadeamento a uma parte interessada, que geralmente envolve uma interface do usuário. A documentação do MSDN para a classe deixa isso bem claro. Se você simplesmente precisar que uma tarefa seja executada em segundo plano, prefira usar um ThreadPoolencadeamento.
Matt Davis
5
Com relação ao seu ponto sobre a System.Windows.Formsassembléia; BackgroundWorkertambém é útil para aplicativos WPF e esses aplicativos podem não ter uma referência ao WinForms.
GiddyUpHorsey
12

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

Para mim, o principal diferencial BackgroundWorkeré a organização automática do evento concluído via 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. Este é um grande diferencial se você estiver usando o BackgroundWorkerem um contexto de interface do usuário.

As tarefas executadas via the ThreadPoolnão podem ser facilmente canceladas (isso inclui ThreadPool. QueueUserWorkItemE os delegados são executados assincronamente). Portanto, embora evite a sobrecarga de spin-thread, se você precisar de cancelamento, use um BackgroundWorkerou (mais provavelmente fora da interface do usuário) gire um thread e mantenha uma referência a ele para poder ligar Abort().

piers7
fonte
1
Apenas ... espero que o aplicativo foi projetado sobre um limpo método de parar uma tarefa de rosca (Abort geralmente não é)
11

Além disso, você está vinculando um encadeamento de threads durante a vida útil do trabalhador em segundo plano, o que pode ser motivo de preocupação, pois há apenas um número finito deles. Eu diria que, se você criar o thread apenas uma vez para o seu aplicativo (e não usar nenhum dos recursos do trabalhador em segundo plano), use um thread, em vez de um thread de trabalho em segundo plano / pool de threads.

Matt
fonte
1
Eu acho que esse é um bom argumento. Portanto, a mensagem que recebo disso é usar um trabalhador em segundo plano se você precisar de um thread em segundo plano "temporariamente" durante a vida útil do formulário, no entanto, se precisar de um thread em segundo plano durante todo o tempo de vida útil do formulário (que pode ser minutos, horas, dias ...), então usar um fio em vez de BackgroundWorker de modo a não utilização indevida o propósito do ThreadPool
freddy smith
Em relação a: "... o que pode ser motivo de preocupação, pois há apenas um número finito deles", você quer dizer que outros aplicativos no sistema operacional podem precisar deles e compartilhar do mesmo 'pool'?
Dan W
8

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

Antes de usar uma BackgroundWorkerconsideração primeiro, se você deseja cancelar um encadeamento (aplicativo de fechamento, cancelamento do usuário), precisa decidir se o encadeamento deve verificar se há cancelamentos ou se deve ser aplicado na própria execução.

BackgroundWorker.CancelAsync()será definido CancellationPendingcomo, truemas não fará mais nada, é responsabilidade dos threads verificar continuamente isso; lembre-se de que você pode acabar com uma condição de corrida nessa abordagem em que o usuário cancelou, mas o thread foi concluído antes do teste para CancellationPending.

Thread.Abort() por outro lado, lançará uma exceção na execução do encadeamento que impõe o cancelamento desse encadeamento, você deve ter cuidado com o que pode ser perigoso, se essa exceção foi repentinamente levantada na execução.

O encadeamento precisa de uma consideração muito cuidadosa, independentemente da tarefa, para algumas leituras adicionais:

Programação paralela nas práticas recomendadas de encadeamento gerenciado do .NET Framework

Brett Ryan
fonte
5

Eu sabia como usar threads antes de conhecer o .NET, por isso demorou um pouco para me acostumar quando comecei a usar 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 depuração. É mais fácil pensar em criar e desligar threads, IMO, do que em dar trabalho a um conjunto de threads.

Ainda não posso comentar as postagens de outras pessoas, portanto, perdoe minha claudicação momentânea ao usar uma resposta para lidar com pilares7

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

ajs410
fonte
2

Se não está quebrado - conserte até que esteja ... apenas brincando :)

Mas, falando sério, o BackgroundWorker provavelmente é muito parecido com o que você já tem, se você o tivesse iniciado desde o início, talvez tivesse economizado algum tempo - mas neste momento não vejo a necessidade. A menos que algo não esteja funcionando, ou você pense que seu código atual é difícil de entender, então eu continuaria com o que você tem.

Gandalf
fonte
2

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

David R Tribble
fonte
2

Quero ressaltar um comportamento da classe BackgroundWorker que ainda não foi mencionado. Você pode criar um Thread normal para executar em segundo plano, definindo a propriedade Thread.IsBackground.

Os threads de segundo plano são idênticos aos de primeiro plano, exceto que os threads de segundo plano não impedem que um processo seja finalizado. [ 1 ]

Você pode testar esse comportamento chamando o seguinte método no construtor da janela do 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 = true; // <--- This will make the thread run in background
    thread.IsBackground = false; // <--- This will delay program termination

    thread.Start();
}

Quando a propriedade IsBackground estiver definida como true e você fechar a janela, seu aplicativo será encerrado normalmente.

Mas quando a propriedade IsBackground estiver definida como false (por padrão) e você fechar a janela, apenas a janela desaparecerá, mas o processo continuará em execução.

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

Doomjunky
fonte
1

Um trabalhador em segundo plano é uma classe que trabalha em um encadeamento separado, mas fornece funcionalidade adicional que você não obtém com um encadeamento simples (como manipulação de relatório de progresso da tarefa).

Se você não precisar dos recursos adicionais fornecidos por um trabalhador em segundo plano - e parece que não precisa -, um Thread será mais apropriado.

Cesar
fonte
-1

O que é desconcertante para mim é que o designer do visual studio permite apenas que você use BackgroundWorkers e Timers que na verdade não funcionam com o projeto de serviço.

Oferece controles de arrastar e soltar em seu serviço, mas ... nem tente implantá-lo. Não vai funcionar.

Serviços: use apenas System.Timers.Timer System.Windows.Forms.Timer não funcionará, mesmo que esteja disponível na caixa de ferramentas

Serviços: BackgroundWorkers não funcionará quando estiver sendo executado como um serviço. Use System.Threading.ThreadPools ou chamadas Async

scottweeden
fonte