Considere um método hipotético de um objeto que faz coisas para você:
public class DoesStuff
{
BackgroundWorker _worker = new BackgroundWorker();
...
public void CancelDoingStuff()
{
_worker.CancelAsync();
//todo: Figure out a way to wait for BackgroundWorker to be cancelled.
}
}
Como alguém pode esperar que um BackgroundWorker seja executado?
No passado, as pessoas tentaram:
while (_worker.IsBusy)
{
Sleep(100);
}
Mas esses bloqueios , porque IsBusy
não são limpos até que o RunWorkerCompleted
evento seja tratado e esse evento não pode ser tratado até que o aplicativo fique ocioso. O aplicativo não ficará ocioso até que o trabalhador termine. (Além disso, é um loop ocupado - nojento.)
Outros adicionaram sugestões de críticas a:
while (_worker.IsBusy)
{
Application.DoEvents();
}
O problema é que isso faz Application.DoEvents()
com que as mensagens atualmente na fila sejam processadas, o que causa problemas de reentrada (o .NET não é reentrante).
Espero usar alguma solução que envolva objetos de sincronização de eventos, onde o código aguarda um evento - RunWorkerCompleted
definido pelos manipuladores de eventos do trabalhador . Algo como:
Event _workerDoneEvent = new WaitHandle();
public void CancelDoingStuff()
{
_worker.CancelAsync();
_workerDoneEvent.WaitOne();
}
private void RunWorkerCompletedEventHandler(sender object, RunWorkerCompletedEventArgs e)
{
_workerDoneEvent.SetEvent();
}
Mas estou de volta ao impasse: o manipulador de eventos não pode ser executado até que o aplicativo fique ocioso e o aplicativo não ficará ocioso porque está aguardando um Evento.
Então, como você pode esperar que um BackgroundWorker termine?
Atualizar As pessoas parecem estar confusas com esta pergunta. Eles parecem pensar que vou usar o BackgroundWorker como:
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += MyWork;
worker.RunWorkerAsync();
WaitForWorkerToFinish(worker);
Não é isso, não é o que estou fazendo e não é o que está sendo perguntado aqui. Se fosse esse o caso, não faria sentido usar um trabalhador em segundo plano.
fonte
((BackgroundWorker)sender).CancellationPending
para obter o evento cancelHá um problema com esta resposta. A interface do usuário precisa continuar processando as mensagens enquanto você aguarda; caso contrário, não será redesenhada, o que será um problema se o trabalhador em segundo plano demorar muito para responder à solicitação de cancelamento.
Uma segunda falha é que
_resetEvent.Set()
nunca será chamada se o segmento de trabalho gerar uma exceção - deixando o segmento principal aguardando indefinidamente - no entanto, essa falha pode ser facilmente corrigida com um bloco try / finalmente.Uma maneira de fazer isso é exibir uma caixa de diálogo modal que possui um timer que verifica repetidamente se o trabalhador em segundo plano concluiu o trabalho (ou o cancelamento no seu caso). Depois que o trabalhador em segundo plano terminar, a caixa de diálogo modal retornará o controle ao seu aplicativo. O usuário não pode interagir com a interface do usuário até que isso aconteça.
Outro método (supondo que você tenha no máximo uma janela sem janela aberta) é definir ActiveForm.Enabled = false e fazer loop em Application, DoEvents até que o trabalhador em segundo plano termine o cancelamento, após o qual você pode definir ActiveForm.Enabled = true novamente.
fonte
Quase todos vocês estão confusos com a pergunta e não estão entendendo como um trabalhador é usado.
Considere um manipulador de eventos RunWorkerComplete:
E tudo está bem.
Agora chega uma situação em que o chamador precisa interromper a contagem regressiva porque precisa executar uma autodestruição de emergência do foguete.
E há também uma situação em que precisamos abrir os portões de acesso ao foguete, mas não durante a contagem regressiva:
E, finalmente, precisamos abastecer o foguete, mas isso não é permitido durante uma contagem regressiva:
Sem a capacidade de aguardar o cancelamento de um trabalhador, devemos mover todos os três métodos para o RunWorkerCompletedEvent:
Agora eu poderia escrever meu código assim, mas não vou. Eu não ligo, simplesmente não sou.
fonte
Você pode verificar o RunWorkerCompletedEventArgs no RunWorkerCompletedEventHandler para ver qual era o status. Sucesso, cancelado ou um erro.
Atualização : para ver se seu trabalhador chamou .CancelAsync () usando isto:
fonte
Vocês não espera o trabalhador em segundo plano concluir. Isso praticamente derrota o propósito de lançar um thread separado. Em vez disso, você deve deixar seu método terminar e mover qualquer código que dependa da conclusão para um local diferente. Você permite que o funcionário avise quando terminar e chame qualquer código restante.
Se você quiser esperar que algo seja concluído, use uma construção de encadeamento diferente que forneça um WaitHandle.
fonte
Por que você não pode simplesmente se vincular ao evento BackgroundWorker.RunWorkerCompleted. É um retorno de chamada que "Ocorrerá quando a operação em segundo plano for concluída, cancelada ou tiver gerado uma exceção".
fonte
Não entendo por que você deseja esperar que um BackgroundWorker seja concluído; realmente parece o exato oposto da motivação da turma.
No entanto, você pode iniciar todos os métodos com uma chamada para worker.IsBusy e fazê-los sair se estiver em execução.
fonte
Só quero dizer que vim aqui porque preciso que um trabalhador em segundo plano aguarde enquanto executava um processo assíncrono enquanto em loop, minha correção foi bem mais fácil do que todas essas outras coisas ^^
Imaginei que compartilharia porque foi aqui que acabei pesquisando uma solução. Além disso, este é o meu primeiro post no estouro de pilha, por isso, se é ruim ou qualquer coisa que eu adoraria críticos! :)
fonte
Hm, talvez eu não esteja acertando sua pergunta.
O trabalhador em segundo plano chama o evento WorkerCompleted quando seu 'workermethod' (o método / função / sub que lida com o backgroundworker.doWork-event ) é concluído, para que não seja necessário verificar se o BW ainda está em execução. Se você deseja interromper seu trabalhador, verifique a propriedade pendente de cancelamento dentro do seu 'método de trabalho'.
fonte
O fluxo de trabalho de um
BackgroundWorker
objeto basicamente requer que você manipule oRunWorkerCompleted
evento para casos de uso de execução normal e cancelamento de usuário. É por isso que a propriedade RunWorkerCompletedEventArgs.Cancelled existe. Basicamente, fazer isso corretamente exige que você considere o método Cancel como um método assíncrono.Aqui está um exemplo:
Se você realmente não deseja que seu método saia, sugiro colocar uma sinalização como uma
AutoResetEvent
em uma derivadaBackgroundWorker
e substituirOnRunWorkerCompleted
para definir a sinalização. Ainda é meio que desajeitado; Eu recomendo tratar o evento cancel como um método assíncrono e fazer o que estiver fazendo noRunWorkerCompleted
manipulador.fonte
Estou um pouco atrasado para a festa aqui (cerca de 4 anos), mas e quanto à configuração de um encadeamento assíncrono que pode lidar com um loop ocupado sem bloquear a interface do usuário, para que o retorno de chamada desse encadeamento seja a confirmação de que o BackgroundWorker concluiu o cancelamento ?
Algo assim:
Em essência, o que isso faz é disparar outro encadeamento para executar em segundo plano, que apenas aguarda seu loop ocupado para ver se o processo
MyWorker
foi concluído. Uma vezMyWorker
terminado o cancelamento, o encadeamento sairá e podemos usá-loAsyncCallback
para executar qualquer método necessário para seguir o cancelamento bem-sucedido - ele funcionará como um evento psuedo. Como isso é separado do segmento da interface do usuário, ele não bloqueará a interface do usuário enquanto aguardamos oMyWorker
término do cancelamento. Se sua intenção é realmente travar e aguardar o cancelamento, isso é inútil para você, mas se você quiser apenas esperar para poder iniciar outro processo, isso funcionará perfeitamente.fonte
Eu sei que isso é muito tarde (5 anos), mas o que você está procurando é usar um Thread e um SynchronizationContext . Você precisará reunir as chamadas da interface do usuário de volta para o thread da interface do usuário "manualmente", em vez de permitir que o Framework faça isso automaticamente.
Isso permite que você use um segmento que você pode esperar, se necessário.
fonte
fonte
A solução de Fredrik Kalseth para esse problema é a melhor que encontrei até agora. O uso de outras soluções
Application.DoEvent()
pode causar problemas ou simplesmente não funcionar. Deixe-me lançar sua solução em uma classe reutilizável. ComoBackgroundWorker
não está selado, podemos derivar nossa classe:Com sinalizadores e bloqueio adequado, garantimos que
_resetEvent.WaitOne()
realmente seja chamado apenas se algum trabalho tiver sido iniciado, caso contrário,_resetEvent.Set();
nunca poderá ser chamado!A tentativa finalmente garante que
_resetEvent.Set();
isso será chamado, mesmo se uma exceção ocorrer em nosso manipulador de DoWork. Caso contrário, o aplicativo poderá congelar para sempre ao chamarCancelSync
!Nós o usaríamos assim:
Você também pode adicionar um manipulador ao
RunWorkerCompleted
evento, como mostrado aqui:BackgroundWorker Class (documentação da Microsoft) .
fonte
Fechar o formulário fecha meu arquivo de log aberto. Meu trabalhador em segundo plano grava esse arquivo de log, então não posso deixar
MainWin_FormClosing()
terminar até que meu trabalhador em segundo plano termine. Se eu não esperar o término do trabalho em segundo plano, ocorrerão exceções.Por que isso é tão difícil?
Um simples
Thread.Sleep(1500)
funciona, mas atrasa o desligamento (se for muito longo) ou causa exceções (se for muito curto).Para desligar logo após o trabalhador em segundo plano terminar, basta usar uma variável. Isso está funcionando para mim:
fonte
Você pode recuar no evento RunWorkerCompleted. Mesmo se você já adicionou um manipulador de eventos para _worker, pode adicionar outro e eles serão executados na ordem em que foram adicionados.
isso pode ser útil se você tiver vários motivos pelos quais um cancelamento pode ocorrer, tornando a lógica de um único manipulador RunWorkerCompleted mais complicada do que você deseja. Por exemplo, cancelando quando um usuário tenta fechar o formulário:
fonte
Uso o
async
método eawait
aguardo o trabalhador terminar seu trabalho:e no
DoWork
método:Você também pode encapsular o
while
loopDoWork
comtry ... catch
para definir_isBusy
comofalse
exceção. Ou simplesmente faça check-_worker.IsBusy
in noStopAsync
loop while.Aqui está um exemplo de implementação completa:
Para parar o trabalhador e esperar que ele seja executado até o fim:
Os problemas com este método são:
fonte
Oh, cara, algumas delas ficaram ridiculamente complexas. tudo o que você precisa fazer é verificar a propriedade BackgroundWorker.CancellationPending dentro do manipulador do DoWork. você pode verificá-lo a qualquer momento. uma vez pendente, defina e.Cancel = True e saia do método.
// método aqui private void Worker_DoWork (objeto remetente, DoWorkEventArgs e) {BackgroundWorker bw = (remetente como BackgroundWorker);
}
fonte