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
- su tarea en segundo plano inspecciona y respeta las
Cancel
propiedad
- 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 :)
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í. –
+1 al comentario de @ Hightechrider. No solo su aplicación no se apagará, sino que mientras no duerma prácticamente bloqueará todo su sistema. –
Por si acaso, ¿ha configurado la propiedad CanBeCancel o así en verdadero para su BackgroundWorker? –