¿Cómo detener BackgroundWorker en el evento de cierre del formulario?

Resuelto THX-1138 asked hace 15 años • 12 respuestas

Tengo un formulario que genera un BackgroundWorker, que debería actualizar el cuadro de texto del formulario (en el hilo principal), por lo tanto, Invoke((Action) (...));llame.
Si HandleClosingEventlo hago, bgWorker.CancelAsync()entonces estoy ObjectDisposedExceptionde Invoke(...)guardia, es comprensible. Pero si me siento HandleClosingEventy espero a que bgWorker termine, entonces .Invoke(...) nunca regresa, también es comprensible.

¿Alguna idea de cómo cierro esta aplicación sin obtener la excepción o el punto muerto?

A continuación se muestran tres métodos relevantes de la clase simple Form1:

    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
    }
THX-1138 avatar Nov 14 '09 02:11 THX-1138
Aceptado

La única forma segura de hacer esto a prueba de interbloqueos y excepciones que conozco es cancelar el evento FormClosing. Establezca e.Cancel = true si BGW aún se está ejecutando y establezca una bandera para indicar que el usuario solicitó un cierre. Luego verifique ese indicador en el controlador de eventos RunWorkerCompleted de BGW y llame a Close() si está configurado.

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...
}
Hans Passant avatar Nov 13 '2009 22:11 Hans Passant

He encontrado otra manera. Si tienes más backgroundWorkers puedes hacer:

List<Thread> bgWorkersThreads  = new List<Thread>();

y en cada método DoWork de backgroundWorker haga:

bgWorkesThreads.Add(Thread.CurrentThread);

Después que puedes usar:

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

Utilicé esto en el complemento de Word en Control, que uso en CustomTaskPane. Si alguien cierra el documento o la aplicación antes de que todos mis backgroundWorkes terminen su trabajo, surgen algunos COM Exception(no recuerdo exactamente cuál). CancelAsync()no funciona.

Pero con esto, puedo cerrar todos los subprocesos utilizados backgroundworkersinmediatamente en DocumentBeforeCloseel evento y mi problema se resuelve.

Bronix avatar Mar 09 '2012 10:03 Bronix

Aquí estaba mi solución (lo siento, está en VB.Net).

Cuando ejecuto el evento FormClosing, ejecuto BackgroundWorker1.CancelAsync() para establecer el valor CancellationPending en True. Desafortunadamente, el programa nunca tiene la oportunidad de verificar el valor CancellationPending para establecer e.Cancel en verdadero (que, hasta donde yo sé, solo se puede hacer en BackgroundWorker1_DoWork). No eliminé esa línea, aunque en realidad no parece hacer ninguna diferencia.

Agregué una línea que establecería mi variable global, bClosingForm, en Verdadero. Luego agregué una línea de código en mi BackgroundWorker_WorkCompleted para verificar tanto e.Cancelled como la variable global, bClosingForm, antes de realizar cualquier paso final.

Con esta plantilla, debería poder cerrar su formulario en cualquier momento, incluso si el trabajador en segundo plano está en medio de algo (lo cual puede no ser bueno, pero seguramente sucederá, por lo que es mejor solucionarlo). No estoy seguro de si es necesario, pero podrías eliminar el trabajador en segundo plano por completo en el evento Form_Closed después de que todo esto haya ocurrido.

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
Tim Hagel avatar Aug 12 '2010 15:08 Tim Hagel

¿No puedes esperar la señal en el destructor del formulario?

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();
}
Cheeso avatar Nov 13 '2009 21:11 Cheeso