Como parar o BackgroundWorker no evento de encerramento do formulário?

Eu tenho um formulário que gera um BackgroundWorker, que deve atualizar a própria checkbox de texto do formulário (no thread principal), portanto, Invoke((Action) (...)); ligar.
Se no HandleClosingEvent eu apenas faço bgWorker.CancelAsync() então eu recebo ObjectDisposedException na chamada Invoke(...) , compreensivelmente. Mas se eu me sentar no HandleClosingEvent e esperar que o bgWorker seja feito, o .Invoke (…) nunca retornará, também compreensivelmente.

Alguma idéia de como fechar este aplicativo sem obter a exceção ou o deadlock?

A seguir estão 3 methods relevantes da class Form1 simples:

  public Form1() { InitializeComponent(); Closing += HandleClosingEvent; this.bgWorker.RunWorkerAsync(); } private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) { while (!this.bgWorker.CancellationPending) { Invoke((Action) (() => { this.textBox1.Text = Environment.TickCount.ToString(); })); } } private void HandleClosingEvent(object sender, CancelEventArgs e) { this.bgWorker.CancelAsync(); /////// while (this.bgWorker.CancellationPending) {} // deadlock } 

A única forma segura de bloqueio e exceção de segurança para fazer isso que eu sei é realmente cancelar o evento FormClosing. Defina e.Cancel = true se o BGW ainda estiver em execução e defina um sinalizador para indicar que o usuário solicitou o fechamento. Em seguida, verifique esse sinalizador no manipulador de events RunWorkerCompleted do BGW e chame Close (), se estiver definido.

 private bool closePending; protected override void OnFormClosing(FormClosingEventArgs e) { if (backgroundWorker1.IsBusy) { closePending = true; backgroundWorker1.CancelAsync(); e.Cancel = true; this.Enabled = false; // or this.Hide() return; } base.OnFormClosing(e); } void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { if (closePending) this.Close(); closePending = false; // etc... } 

Eu encontrei outro caminho. Se você tem mais backgroundWorkers, você pode fazer:

 List bgWorkersThreads = new List(); 

e em cada método doWorker DoWork make:

 bgWorkesThreads.Add(Thread.CurrentThread); 

Arter que você pode usar:

 foreach (Thread thread in this.bgWorkersThreads) { thread.Abort(); } 

Eu usei isso no Word Add-in no controle, que eu uso em CustomTaskPane . Se alguém fechar o documento ou a aplicação mais cedo, então todas as minhas backgroundWorkes terminam seu trabalho, isso gera uma COM Exception (não me lembro exatamente de qual). CancelAsync() não funciona.

Mas com isso, eu posso fechar todos os tópicos que são usados ​​por backgroundworkers imediatamente no evento DocumentBeforeClose e meu problema é resolvido.

Aqui foi a minha solução (Desculpe, é em VB.Net).

Quando executo o evento FormClosing eu corro BackgroundWorker1.CancelAsync () para definir o valor de CancellationPending como True. Infelizmente, o programa nunca tem a chance de verificar o valor de CancellationPending para definir e.Cancel como true (o que, até onde posso dizer, só pode ser feito em BackgroundWorker1_DoWork). Eu não removi essa linha, embora não pareça realmente fazer diferença.

Eu adicionei uma linha que definiria minha variável global, bClosingForm, como True. Então eu adicionei uma linha de código no meu BackgroundWorker_WorkCompleted para verificar tanto o e.Cancelled como a variável global, bClosingForm, antes de executar qualquer etapa final.

Usando este modelo, você deve poder fechar seu formulário a qualquer momento, mesmo que o agente de segundo plano esteja no meio de algo (o que pode não ser bom, mas está prestes a acontecer, assim também pode ser resolvido). Não tenho certeza se é necessário, mas você pode descartar o trabalhador em segundo plano inteiramente no evento Form_Closed depois que tudo isso ocorrer.

 Private bClosingForm As Boolean = False Private Sub SomeFormName_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing bClosingForm = True BackgroundWorker1.CancelAsync() End Sub Private Sub backgroundWorker1_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork 'Run background tasks: If BackgroundWorker1.CancellationPending Then e.Cancel = True Else 'Background work here End If End Sub Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As System.Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted If Not bClosingForm Then If Not e.Cancelled Then 'Completion Work here End If End If End Sub 

Você não pode esperar o sinal no destruidor do formulário?

 AutoResetEvent workerDone = new AutoResetEvent(); private void HandleClosingEvent(object sender, CancelEventArgs e) { this.bgWorker.CancelAsync(); } private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) { while (!this.bgWorker.CancellationPending) { Invoke((Action) (() => { this.textBox1.Text = Environment.TickCount.ToString(); })); } } private ~Form1() { workerDone.WaitOne(); } void backgroundWorker1_RunWorkerCompleted( Object sender, RunWorkerCompletedEventArgs e ) { workerDone.Set(); } 

Em primeiro lugar, o ObjectDisposedException é apenas uma armadilha possível aqui. Executar o código do OP produziu o seguinte InvalidOperationException em um número substancial de ocasiões:

Invoke ou BeginInvoke não podem ser chamados em um controle até que o identificador de janela tenha sido criado.

Suponho que isso poderia ser alterado iniciando o trabalhador no retorno de chamada ‘Carregado’ em vez do construtor, mas toda essa provação pode ser totalmente evitada se o mecanismo de relatório de Progresso do BackgroundWorker for usado. O seguinte funciona bem:

 private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) { while (!this.bgWorker.CancellationPending) { this.bgWorker.ReportProgress(Environment.TickCount); Thread.Sleep(1); } } private void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e) { this.textBox1.Text = e.ProgressPercentage.ToString(); } 

Eu meio que sequestrei o parâmetro percentual, mas é possível usar a outra sobrecarga para passar qualquer parâmetro.

É interessante notar que a remoção da chamada sleep acima entope a interface do usuário, consome alta CPU e aumenta continuamente o uso da memory. Eu acho que tem algo a ver com a fila de mensagens da GUI sendo sobrecarregada. No entanto, com a chamada em espera intacta, o uso da CPU é virtualmente igual a 0 e o uso da memory também parece bom. Para ser prudente, talvez seja necessário usar um valor maior que 1 ms? Uma opinião de especialista aqui seria apreciada … Atualização : Parece que, desde que a atualização não seja muito freqüente, ela deve estar OK: Link

De qualquer forma, não posso prever um cenário em que a atualização da GUI tenha que estar em intervalos menores do que alguns milissegundos (pelo menos, em cenários onde um humano está observando a GUI), então acho que na maioria das vezes relatórios de progresso seria a escolha certa

O trabalhador de segundo plano não deve usar o Invoke para atualizar a checkbox de texto. Ele deve pedir ao thread da interface do usuário para atualizar a checkbox de texto usando o evento ProgressChanged com o valor a ser colocado na checkbox de texto anexada.

Durante o evento Fechado (ou talvez o encerramento do evento), o thread da interface do usuário lembra que o formulário está fechado antes de cancelar o trabalho em segundo plano.

Ao receber o progressChanged, o thread da interface do usuário verifica se o formulário está fechado e, somente se não, atualiza a checkbox de texto.

Isso não funcionará para todos, mas se você estiver fazendo algo em um BackgroundWorker periodicamente, como a cada segundo ou a cada 10 segundos (talvez pesquisando um servidor), isso parece funcionar bem para interromper o processo de maneira ordenada e sem mensagens de erro. (pelo menos até agora) e é fácil de seguir;

  public void StopPoll() { MyBackgroundWorker.CancelAsync(); //Cancel background worker AutoResetEvent1.Set(); //Release delay so cancellation occurs soon } private void bw_DoWork(object sender, DoWorkEventArgs e) { while (!MyBackgroundWorker.CancellationPending) { //Do some background stuff MyBackgroundWorker.ReportProgress(0, (object)SomeData); AutoResetEvent1.WaitOne(10000); } } 

Eu realmente não vejo porque DoEvents é considerado como uma má escolha, neste caso, se você estiver usando this.enabled = false. Eu acho que isso seria bem legal.

 protected override void OnFormClosing(FormClosingEventArgs e) { this.Enabled = false; // or this.Hide() e.Cancel = true; backgroundWorker1.CancelAsync(); while (backgroundWorker1.IsBusy) { Application.DoEvents(); } e.cancel = false; base.OnFormClosing(e); } 

Eu passaria o SynchronizationContext associado à checkbox de texto para o BackgroundWorker e usaria isso para executar atualizações no thread da interface do usuário. Usando SynchronizationContext.Post, você pode verificar se o controle está descartado ou descartado.

E sobre o Me.IsHandleCreated?

  Private Sub BwDownload_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BwDownload.RunWorkerCompleted If Me.IsHandleCreated Then 'Form is still open, so proceed End If End Sub 

Outra maneira:

 if (backgroundWorker.IsBusy) { backgroundWorker.CancelAsync(); while (backgroundWorker.IsBusy) { Application.DoEvents(); } } 

Uma solução que funciona, mas muito complicada. A idéia é gerar o cronômetro que continuará tentando fechar o formulário, e o formulário recusará fechar até que o bgWorker esteja morto.

 private void HandleClosingEvent(object sender, CancelEventArgs e) { if (!this.bgWorker.IsBusy) { // bgWorker is dead, let Closing event proceed. e.Cancel = false; return; } if (!this.bgWorker.CancellationPending) { // it is first call to Closing, cancel the bgWorker. this.bgWorker.CancelAsync(); this.timer1.Enabled = true; } // either this is first attempt to close the form, or bgWorker isn't dead. e.Cancel = true; } private void timer1_Tick(object sender, EventArgs e) { Trace.WriteLine("Trying to close..."); Close(); }