¿Cómo detener BackgroundWorker en el evento de cierre del formulario?
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 HandleClosingEvent
lo hago, bgWorker.CancelAsync()
entonces estoy ObjectDisposedException
de Invoke(...)
guardia, es comprensible. Pero si me siento HandleClosingEvent
y 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
}
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...
}
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 backgroundworkers
inmediatamente en DocumentBeforeClose
el evento y mi problema se resuelve.
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
¿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();
}