2010-03-09 9 views
11

Cuando se cierra mi aplicación C#, a veces queda atrapada en la rutina de limpieza. Específicamente, un trabajador de segundo plano no se está cerrando. Esta es básicamente la forma en que estoy tratando de cerrarla:Cancelar tareas de fondo

privada App_FormClosing vacío (remitente del objeto, FormClosingEventArgs e) { backgroundWorker1.CancelAsync(); while (backgroundWorker1.IsBusy); // Se queda atrapado aquí. }

¿Hay una manera diferente de que debería estar haciendo esto? Estoy usando Microsoft Visual C# 2008 Express Edition. Gracias.

INFORMACIÓN ADICIONAL:

El trabajador fondo no parece ser la salida. Esto es lo que tengo:

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) 
{ 
    while (!backgroundWorker1.CancellationPending) 
    { 
     // Do something. 
    } 
} 

También hemos modificado el código de limpieza:

private void App_FormClosing(object sender, FormClosingEventArgs e) 
{ 
    while (backgroundWorker1.IsBusy) 
    { 
     backgroundWorker1.CancelAsync(); 
     System.Threading.Thread.Sleep(1000); 
    } 
} 

¿Hay alguna otra cosa que debo hacer?

+12

Nunca se debe ejecutar un bucle de sondeo que gira como esta, que va a consumir ciclos de CPU haciendo más difícil para los otros hilos para completar su trabajo. Si no puede usar un WaitHandle o una primitiva de sincronización similar y absolutamente debe sondear, agregue un Thread.Sleep (x) allí. –

+0

+1 al comentario de @ Hightechrider. No solo su aplicación no se apagará, sino que mientras no duerma prácticamente bloqueará todo su sistema. –

+0

Por si acaso, ¿ha configurado la propiedad CanBeCancel o así en verdadero para su BackgroundWorker? –

Respuesta

4

Kevin Gale lo cierto al afirmar que el manejador de su DoWork BackgroundWorker necesita para sondear CancellationPending y regresar si la cancelación se solicita.

Dicho esto, si esto sucede cuando la aplicación se está cerrando, también puede ignorarla de forma segura. BackgroundWorker usa un hilo ThreadPool, que es, por definición, un hilo de fondo. Dejar esta ejecución no evitará que su aplicación finalice, y el hilo se cortará automáticamente cuando se cierre la aplicación.

+1

+1 Absolutamente, a menos que el hilo del trabajador de fondo necesite realizar algún tipo de limpieza antes de que se cierre. –

+0

@Kevin: Cierto, pero dado que el OP simplemente intentaba cancelar el hilo, parece que ese no es el caso ... –

+0

Gracias, Reed. Este parece ser un caso en el que estaba haciendo algo simple mucho más difícil de lo necesario. –

4

En el hilo del trabajador de fondo, debe marcar el indicador BackgroundWorker.CancellationPending y salir si es verdadero.

El CancelAsync() simplemente establece este indicador.

O para decirlo de otra manera. CancelAsync() en realidad no cancela nada. No abortará el hilo ni hará que salga. Si el hilo de trabajo está en un bucle y comprueba el indicador de cancelación de cancelación periódicamente, puede capturar la solicitud de cancelación y salir.

MSDN tiene un ejemplo here aunque no utiliza un bucle en la rutina de trabajo.

+2

cerca, pero no del todo. su tarea de fondo debería inspeccionar [¡y respetar!] su propiedad 'DoWorkEventArgs.Cancel'. a menos que esté compartiendo variables de instancia entre subprocesos - desaconsejado - la tarea en segundo plano no tendrá acceso a la instancia real de 'BackgroundWorker'. –

+0

El propio ejemplo de Microsoft accede al trabajador en segundo plano a través del parámetro del remitente. Y en cualquier caso, no me referí a su variable para el trabajador de fondo, sino a la propiedad de la clase. Para hacerlo más claro, agregué un enlace a un ejemplo. –

0

Probar:

if (this.backgroundWorker1.IsBusy) this.backgroundWorker1.CancelAsync(); 
1

Este código está garantizado a un punto muerto cuando el BGW aún se está ejecutando. BGW no puede completar hasta que su evento RunWorkerCompleted termine de ejecutarse. RunWorkerCompleted no se puede ejecutar hasta que el subproceso de la IU permanezca inactivo y ejecute el ciclo de mensajes. Pero el subproceso de interfaz de usuario no está inactivo, está atascado en el ciclo while.

Si desea que el hilo BGW se complete limpiamente, tiene para mantener su formulario con vida. Consulte this thread para ver cómo hacerlo.

7

Algunas sugerencias bastante buenas, pero no creo que resuelvan el problema subyacente: cancelar una tarea en segundo plano.

Desafortunadamente, al usar BackgroundWorker, la finalización de su tarea depende de la tarea en sí. La única forma en que su bucle while finalizará es si su tarea en segundo plano comprueba su propiedad Cancel y devuelve o rompe su proceso actual.

Ejemplo Base

Por ejemplo, considere

private readonly BackgroundWorker worker = new BackgroundWorker(); 

public void SomeFormEventForStartingBackgroundTask() 
{ 
    worker.DoWork += BackgroundTask_HotelCalifornia; 
    worker.WorkerSupportsCancellation = true; 
    worker.RunWorkerAsync(); 
} 

// semantically, you want to perform this task for lifetime of 
// application, you may even expect that calling CancelAsync 
// will out and out abort this method - that is incorrect. 
// CancelAsync will only set DoWorkEventArgs.Cancel property 
// to true 
private void BackgroundTask_HotelCalifornia (object sender, DoWorkEventArgs e) 
{ 
    for (; ;) 
    { 
     // because we never inspect e.Cancel, we can never leave! 
    } 
} 

private void App_FormClosing(object sender, FormClosingEventArgs e)  
{ 
    // [politely] request termination 
    worker.CancelAsync(); 

    // [politely] wait until background task terminates 
    while (worker.IsBusy); 
} 

Esto es lo que está sucediendo por defecto. Ahora, tal vez su tarea no sea un ciclo infinito, tal vez sea solo una tarea de larga duración. De cualquier manera, su hilo principal bloqueará [en realidad está girando, pero lo hará] hasta que la tarea se complete, o no según sea el caso.

Si ha escrito personalmente y puede modificar la tarea, entonces tiene algunas opciones.

Ejemplo Mejora

Por ejemplo, esta es una mejor aplicación del ejemplo anterior

private readonly BackgroundWorker worker = new BackgroundWorker(); 

// this is used to signal our main Gui thread that background 
// task has completed 
private readonly AutoResetEvent isWorkerStopped = 
    new AutoResentEvent (false); 

public void SomeFormEventForStartingBackgroundTask() 
{ 
    worker.DoWork += BackgroundTask_HotelCalifornia; 
    worker.RunWorkerCompleted += BackgroundTask_Completed; 
    worker.WorkerSupportsCancellation = true; 
    worker.RunWorkerAsync(); 
} 

private void BackgroundTask_HotelCalifornia (object sender, DoWorkEventArgs e) 
{ 
    // execute until canceled 
    for (; !e.Cancel;) 
    { 
     // keep in mind, this task will *block* main 
     // thread until cancel flag is checked again, 
     // so if you are, say crunching SETI numbers 
     // here for instance, you could still be blocking 
     // a long time. but long time is better than 
     // forever ;) 
    } 
} 

private void BackgroundTask_Completed (
    object sender, 
    RunWorkerCompletedEventArgs e) 
{ 
    // ok, our task has stopped, set signal to 'signaled' state 
    // we are complete! 
    isStopped.Set(); 
} 

private void App_FormClosing(object sender, FormClosingEventArgs e)  
{ 
    // [politely] request termination 
    worker.CancelAsync(); 

    // [politely] wait until background task terminates 
    isStopped.WaitOne(); 
} 

Mientras que esto es mejor, no es tan buena como la que podría ser. Si puede estar [razonablemente] seguro de que su tarea de fondo finalizará, esto puede ser "lo suficientemente bueno".

Sin embargo, lo que [normalmente] queremos, es algo como esto

private void App_FormClosing(object sender, FormClosingEventArgs e)  
{ 
    // [politely] request termination 
    worker.CancelAsync(); 

    // [politely] wait until background task terminates 
    TimeSpan gracePeriod = TimeSpan.FromMilliseconds(100); 
    bool isStoppedGracefully = isStopped.WaitOne (gracePeriod); 

    if (!isStoppedGracefully) 
    { 
     // KILL! KILL! KILL! 
    } 
} 

Por desgracia, no podemos. BackgroundWorker no expone ningún medio de terminación forzada. Esto se debe a que se trata de una abstracción construida sobre algún sistema oculto de gestión de subprocesos, que podría desestabilizar potencialmente otras partes de su aplicación si se terminara a la fuerza.

El único medio [que he visto al menos] para implementar lo anterior es administrar su propio enhebrado.

ejemplo ideal

Así, por ejemplo

private Thread worker = null; 

// this time, 'Thread' provides all synchronization 
// constructs required for main thread to synchronize 
// with background task. however, in the interest of 
// giving background task a chance to terminate gracefully 
// we supply it with this cancel signal 
private readonly AutoResetEvent isCanceled = new AutoResentEvent (false); 

public void SomeFormEventForStartingBackgroundTask() 
{ 
    worker = new Thread (BackgroundTask_HotelCalifornia); 
    worker.IsBackground = true; 
    worker.Name = "Some Background Task"; // always handy to name things! 
    worker.Start(); 
} 

private void BackgroundTask_HotelCalifornia() 
{ 
    // inspect cancel signal, no wait period 
    // 
    // NOTE: so cheating here a bit, this is an instance variable 
    // but could as easily be supplied via parameterized thread 
    // start delegate 
    for (; !isCanceled.WaitOne (0);) 
    { 
    } 
} 

private void App_FormClosing(object sender, FormClosingEventArgs e)  
{ 
    // [politely] request termination 
    isCanceled.Set(); 

    // [politely] wait until background task terminates 
    TimeSpan gracePeriod = TimeSpan.FromMilliseconds(100); 
    bool isStoppedGracefully = worker.Join (gracePeriod); 

    if (!isStoppedGracefully) 
    { 
     // wipe them out, all of them. 
     worker.Abort(); 
    } 
} 

Y que hay, es una introducción decente en la gestión de hilo.

¿Cuál es el más adecuado para usted? Depende de tu aplicación. Es probablemente el mejor no a mover el bote, y modificar su actual implementación para asegurar que

  1. su tarea en segundo plano inspecciona y respeta las Cancel propiedad
  2. sus principales hilo espera para la finalización, en comparación con el sondeo

Es muy importante comparar y evaluar los pros y los contras de cada enfoque.

Si necesidad de control y garantía de terminación de de otra persona tareas, a continuación, escribir un sistema de gestión de hilo que incorpora el anterior puede ser el camino a seguir. Sin embargo, usted perdería las funciones listas para usar, como la agrupación de subprocesos, el informe de progreso, la clasificación de datos entre hilos [el trabajador lo hace, ¿no?], Y un montón de otras cosas. Sin mencionar, "rodar el tuyo" a menudo es propenso a errores.

De todos modos, espero que esto ayude :)

+0

¿Tienes miedo de escribir 'while'? – martijnn2008

Cuestiones relacionadas