2010-01-08 15 views
5

Estoy usando un BackgroundWorker para realizar un cálculo largo (solo uno de estos cálculos a la vez).Cancelar un BackgroundWorker, cómo evitar la carrera cuando ya ha terminado

El usuario tiene la posibilidad de cancelar el trabajador (llamando a worker.CancelAsync).

En el método worker.DoWork reviso periódicamente el indicador de cancelar pendiente y luego regreso del método.

Luego se completa el evento Completado del trabajador y puedo verificar que el trabajador haya sido cancelado. Además, y eso es lo importante, realizo una limpieza adicional cuando se detecta una cancelación.

Estoy seguro de que podría haber un problema si el usuario cancela al trabajador y ya ha sido devuelto por el método DoWork. En ese caso, me gustaría saber que el trabajador fue cancelado para poder realizar la limpieza ...

¿Hay una forma mejor de manejar el procedimiento de cancelación, con la limpieza, de un trabajador?

Respuesta

2

Su manejador de eventos DoWork shoud comprobar periódicamente BackgroundWorker.CancellationPending, y establecer DoWorkEventArgs.Cancel true antes de regresar si se canceló.

Su controlador de eventos RunWorkerCompleted debe comprobar la propiedad RunWorkerCompletedEventArgs.Cancelled para determinar si el manejador DoWork evento cancelado (DoWorkEventArgs.Cancel establecido en true).

En caso de una condición de carrera, puede ocurrir que el usuario solicite la cancelación (BackgroundWorker.CancellationPending es cierto) pero el trabajador no lo vio (RunWorkerCompletedEventArgs.Cancelled es falso). Puede probar estas dos propiedades para determinar si esto ha ocurrido y hacer lo que elija (ya sea como finalización exitosa, porque el trabajador realmente terminó satisfactoriamente o como cancelación) porque el usuario canceló y no le importa nada. Más).

No veo ninguna situación donde haya alguna ambigüedad sobre lo que sucedió.

EDITAR

En respuesta al comentario - si hay varias clases que necesitan para detectar CancellationPending, no hay realmente ninguna alternativa para pasar a estas clases una referencia a un tipo como BackgroundWorker que les permite recupera esta información Puede resumir esto en una interfaz, que es lo que generalmente hago como se describe en esta respuesta a una pregunta sobre BackgroundWorkers. Pero aún necesita pasar una referencia a un tipo que implemente esta interfaz para sus clases de trabajadores.

Si desea que sus clases de trabajador puedan configurar DoWorkEventArgs.Cancel, tendrá que pasar una referencia a esto o adoptar una convención diferente (por ejemplo, un valor de retorno booleano o una excepción personalizada) que permita a sus clases de trabajadores indicar esa cancelación ha ocurrido.

+1

Esa es una explicación muy clara. Sin embargo, dado que el trabajo no se realiza únicamente en el método "DoWork", tendría que pasar los EventArgs a todas las clases que verificarían la cancelación y tengo alrededor de 30 de esas clases (problema de optimización profunda). Esto es factible pero creo que sería un PITA ... Tampoco me gusta que esas clases tengan que gestionar la cancelación. Todavía estoy buscando una mejor manera. –

+0

"Tampoco me gustan esas clases que tienen que gestionar la cancelación" - No entiendo su preocupación. Si es así, ¡no haga que administren la cancelación! En su lugar, solo verifique la cancelación cuando una de estas clases devuelva el control a su método principal de DoWork. – Joe

+0

Esas clases internas están haciendo un cálculo realmente largo y, por lo tanto, no regresan con frecuencia. –

0

¿Hay algún motivo por el que no pueda realizar la limpieza al final del método que ejecuta de forma asíncrona?

por lo general utilizan una estructura como esta

private void MyThreadMethod() 
{ 
    try 
    { 
     // Thread code 
    } 
    finally 
    { 
     // Cleanup 
    } 
} 

sólo he usar el evento completo para realizar tareas como actualizar la interfaz de usuario, no para limpiar después de la rosca.

+1

Sí, no puedo hacer la limpieza en el trabajo en segundo plano ... Tengo que hacerlo en el hilo de la interfaz de usuario. –

+1

¿Ve el fuego del evento más de una vez? Si es así, puedes usar un bloqueador de bloqueo (...) {} en tu controlador de eventos para asegurarte de que el código solo pueda ejecutarse de a una por vez, y establecer un indicador que indique que la limpieza está completa (verifica ese indicador lo primero en tu bloque de bloqueo y solo ejecuta el código de limpieza si no está configurado). –

0

Sin ver su código es un poco difícil hacer sugerencias, pero una cosa que podría hacer es deshabilitar el botón "Cancelar" cuando el usuario hace clic en él y también al salir del método DoWork.

Si el usuario también puede cancelar presionando una tecla, también deberá desactivarlo.

Además, si el método DoWork ha finalizado, el usuario no tiene nada que "Cancelar", o me falta algo. En ese caso, incluso si no desactiva el botón Cancelar, no hay nada adicional que deba hacer.

¿Por qué necesita ver el indicador cancelar después de que el trabajador haya hecho su trabajo? ¿Aún quieres revertir lo que haya cambiado?

+0

De hecho, el problema no impide que el usuario cancele varias veces ... El problema está más en asegurarse de ver el indicador cancelar incluso DESPUÉS de que el trabajador haya hecho su trabajo y se haya recibido el evento Completado. –

1

Se garantiza que no tendrá una carrera en el controlador de eventos RunWorkerCompleted ya que se ejecuta en el subproceso de interfaz de usuario. Esto siempre debería funcionar:

private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { 
    var bgw = sender as BackgroundWorker; 
    if (e.Cancelled || bgw.CancellationPending) { 
    // etc.. 
    } 
} 
+0

Parece que no funciona. Ver mi comentario sobre la respuesta anterior. – user12861

2

No creo que las otras respuestas aquí realmente resuelvan la posible condición de carrera en todos los casos. El problema es que CancellationPending parece volver a ser falso después del final de DoWork independientemente de si DoWork realmente tuvo la oportunidad de examinar la bandera. Puede probar esto usted mismo si lo desea. Para solucionar este problema, tuve que crear una subclase, así:

Public Class CancelTrackingBackgroundWorker 
    Inherits System.ComponentModel.BackgroundWorker 
    Public CancelRequested As Boolean = False 
    Public Sub TrackableCancelAsync() 
    Me.CancelRequested = True 
    Me.CancelAsync() 
    End Sub 
End Class 

(¿Qué tan molesto CancelAsync no es reemplazable, por cierto.) Entonces me llamo TrackableCancelAsync lugar de la antigua CancelAsync y puedo comprobar si hay este nuevo indicador CancelRequested en mi código. Siempre que solo llame y verifique desde el hilo de UI (como debería ser el estándar), eso debería encargarse de sus problemas de enhebrado.

Cuestiones relacionadas