Considere un método hipotético de un objeto que hace cosas por usted:
public class DoesStuff
{
BackgroundWorker _worker = new BackgroundWorker();
...
public void CancelDoingStuff()
{
_worker.CancelAsync();
//todo: Figure out a way to wait for BackgroundWorker to be cancelled.
}
}
¿Cómo se puede esperar a que se realice un BackgroundWorker?
En el pasado la gente ha intentado:
while (_worker.IsBusy)
{
Sleep(100);
}
Pero esto se interrumpe , porque IsBusy
no se borra hasta después de RunWorkerCompleted
que se maneja el evento, y ese evento no se puede manejar hasta que la aplicación quede inactiva. La aplicación no estará inactiva hasta que el trabajador haya terminado. (Además, es un circuito ocupado, desagradable).
Otros han agregado sugerencias para incluirlo en:
while (_worker.IsBusy)
{
Application.DoEvents();
}
El problema con esto es que Application.DoEvents()
hace que se procesen los mensajes actualmente en la cola, lo que causa problemas de reincorporación (.NET no es reentrante).
Espero usar alguna solución que involucre objetos de sincronización de eventos, donde el código espera un evento, que los RunWorkerCompleted
manejadores de eventos del trabajador establecen. Algo como:
Event _workerDoneEvent = new WaitHandle();
public void CancelDoingStuff()
{
_worker.CancelAsync();
_workerDoneEvent.WaitOne();
}
private void RunWorkerCompletedEventHandler(sender object, RunWorkerCompletedEventArgs e)
{
_workerDoneEvent.SetEvent();
}
Pero volví al punto muerto: el controlador de eventos no puede ejecutarse hasta que la aplicación esté inactiva, y la aplicación no estará inactiva porque está esperando un Evento.
Entonces, ¿cómo puedes esperar a que termine un BackgroundWorker?
Actualización La gente parece estar confundida por esta pregunta. Parecen pensar que usaré el BackgroundWorker como:
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += MyWork;
worker.RunWorkerAsync();
WaitForWorkerToFinish(worker);
Es decir , no se, es decir , no lo que estoy haciendo, y que es no lo que se pide aquí. Si ese fuera el caso, no tendría sentido usar un trabajador de fondo.
fuente
((BackgroundWorker)sender).CancellationPending
para obtener el evento de cancelaciónHay un problema con esta respuesta. La interfaz de usuario debe continuar procesando los mensajes mientras espera, de lo contrario no se volverá a pintar, lo que será un problema si su trabajador en segundo plano tarda mucho en responder a la solicitud de cancelación.
Una segunda falla es que
_resetEvent.Set()
nunca se llamará si el subproceso de trabajo produce una excepción, dejando que el subproceso principal espere indefinidamente, sin embargo, esta falla podría solucionarse fácilmente con un bloque try / finalmente.Una forma de hacerlo es mostrar un cuadro de diálogo modal que tiene un temporizador que verifica repetidamente si el trabajador en segundo plano ha terminado el trabajo (o ha cancelado en su caso). Una vez que el trabajador en segundo plano ha finalizado, el cuadro de diálogo modal devuelve el control a su aplicación. El usuario no puede interactuar con la interfaz de usuario hasta que esto suceda.
Otro método (suponiendo que tenga un máximo de una ventana sin modo abierta) es establecer ActiveForm.Enabled = false, luego hacer un bucle en Application, DoEvents hasta que el trabajador en segundo plano haya terminado de cancelar, después de lo cual puede configurar ActiveForm.Enabled = true nuevamente.
fuente
Casi todos ustedes están confundidos por la pregunta y no entienden cómo se usa un trabajador.
Considere un controlador de eventos RunWorkerComplete:
Y todo esta bien.
Ahora llega una situación en la que la persona que llama necesita abortar la cuenta regresiva porque necesita ejecutar una autodestrucción de emergencia del cohete.
Y también hay una situación en la que necesitamos abrir las puertas de acceso al cohete, pero no mientras hacemos una cuenta regresiva:
Y, por último, necesitamos eliminar el combustible del cohete, pero eso no está permitido durante una cuenta regresiva:
Sin la capacidad de esperar a que un trabajador cancele, debemos mover los tres métodos al RunWorkerCompletedEvent:
Ahora podría escribir mi código así, pero no voy a hacerlo. No me importa, simplemente no lo soy.
fuente
Puede registrarse en RunWorkerCompletedEventArgs en RunWorkerCompletedEventHandler para ver cuál era el estado. Éxito, cancelado o un error.
Actualización : para ver si su trabajador ha llamado .CancelAsync () usando esto:
fuente
Usted no espera a que el trabajador a fondo completa. Eso prácticamente anula el propósito de lanzar un hilo separado. En su lugar, debe dejar que termine su método y mover cualquier código que dependa de la finalización a un lugar diferente. Dejas que el trabajador te diga cuándo está hecho y luego llama a cualquier código restante.
Si desea esperar a que se complete algo, use una construcción de subprocesos diferente que proporcione un WaitHandle.
fuente
¿Por qué no puedes simplemente conectarte al evento BackgroundWorker.RunWorkerCompleted? Es una devolución de llamada que "ocurrirá cuando la operación en segundo plano se haya completado, se haya cancelado o se haya producido una excepción".
fuente
No entiendo por qué querrías esperar a que se complete un BackgroundWorker; realmente parece exactamente lo contrario de la motivación para la clase.
Sin embargo, puede iniciar cada método con una llamada al trabajador. IsBusy y hacer que salgan si se está ejecutando.
fuente
Solo quiero decir que vine aquí porque necesito que un trabajador en segundo plano espere mientras ejecutaba un proceso asíncrono mientras estaba en un bucle, mi solución fue mucho más fácil que todas estas otras cosas ^^
Solo pensé en compartir porque aquí es donde terminé mientras buscaba una solución. Además, esta es mi primera publicación en el desbordamiento de pila, así que si es malo o algo así, ¡me encantarían las críticas! :)
fuente
Tal vez no estoy respondiendo bien tu pregunta.
El trabajador de fondo llama al evento WorkerCompleted una vez que su 'método de trabajo' (el método / función / sub que maneja el evento backgroundworker.doWork-event ) está terminado, por lo que no es necesario verificar si el BW aún se está ejecutando. Si desea detener a su trabajador, verifique la propiedad pendiente de cancelación dentro de su 'método de trabajador'.
fuente
El flujo de trabajo de un
BackgroundWorker
objeto básicamente requiere que maneje elRunWorkerCompleted
evento tanto para la ejecución normal como para los casos de uso de cancelación del usuario. Esta es la razón por la cual existe la propiedad RunWorkerCompletedEventArgs.Cancelled . Básicamente, hacer esto correctamente requiere que consideres que tu método Cancel es un método asincrónico en sí mismo.Aquí hay un ejemplo:
Si realmente no desea que su método salga, le sugiero que coloque una bandera como una
AutoResetEvent
en un derivadoBackgroundWorker
, luego anuleOnRunWorkerCompleted
para establecer la bandera. Sin embargo, todavía es un poco torpe; Recomiendo tratar el evento cancel como un método asincrónico y hacer lo que esté haciendo actualmente en elRunWorkerCompleted
controlador.fuente
Llego un poco tarde a la fiesta aquí (aproximadamente 4 años), pero ¿qué pasa con la configuración de un subproceso asincrónico que puede manejar un bucle ocupado sin bloquear la interfaz de usuario, luego hacer que la devolución de llamada de ese subproceso sea la confirmación de que BackgroundWorker ha terminado de cancelar ?
Algo como esto:
En esencia, lo que esto hace es disparar otro hilo para ejecutarse en segundo plano que solo espera en su bucle ocupado para ver si se
MyWorker
ha completado. Una vez queMyWorker
haya finalizado la cancelación, el subproceso saldrá y podemos usarloAsyncCallback
para ejecutar cualquier método que necesitemos para seguir la cancelación exitosa: funcionará como un evento psuedo. Dado que esto está separado del hilo de la interfaz de usuario, no bloqueará la interfaz de usuario mientras esperamos aMyWorker
que finalice la cancelación. Si su intención realmente es bloquear y esperar la cancelación, entonces esto es inútil para usted, pero si solo desea esperar para poder comenzar otro proceso, entonces esto funciona muy bien.fuente
Sé que esto es realmente tarde (5 años), pero lo que está buscando es usar un hilo y un contexto de sincronización . Tendrá que ordenar las llamadas de la interfaz de usuario al hilo de la interfaz de usuario "a mano" en lugar de dejar que el Marco lo haga automáticamente.
Esto le permite usar un hilo que puede esperar si es necesario.
fuente
fuente
La solución de Fredrik Kalseth a este problema es la mejor que he encontrado hasta ahora. Se utilizan otras soluciones
Application.DoEvent()
que pueden causar problemas o simplemente no funcionan. Déjame convertir su solución en una clase reutilizable. ComoBackgroundWorker
no está sellado, podemos derivar nuestra clase de él:Con banderas y un bloqueo adecuado, nos aseguramos de que
_resetEvent.WaitOne()
realmente solo se llame si se ha iniciado algún trabajo, de lo contrario, ¡_resetEvent.Set();
tal vez nunca se llame!El try-finally asegura que
_resetEvent.Set();
se llamará, incluso si se produce una excepción en nuestro controlador DoWork. De lo contrario, la aplicación podría congelarse para siempre al llamarCancelSync
.Lo usaríamos así:
También puede agregar un controlador al
RunWorkerCompleted
evento como se muestra aquí:Clase BackgroundWorker (documentación de Microsoft) .
fuente
Cerrar el formulario cierra mi archivo de registro abierto. Mi trabajador en segundo plano escribe ese archivo de registro, por lo que no puedo dejar de
MainWin_FormClosing()
terminar hasta que termine mi trabajador en segundo plano. Si no espero a que termine mi trabajador en segundo plano, se producen excepciones.¿Por qué es tan difícil?
Un simple
Thread.Sleep(1500)
funciona, pero retrasa el apagado (si es demasiado largo) o causa excepciones (si es demasiado corto).Para cerrar justo después de que termine el trabajador en segundo plano, simplemente use una variable. Esto es trabajo para mí:
fuente
Puede recuperar el evento RunWorkerCompleted. Incluso si ya ha agregado un controlador de eventos para _worker, puede agregar otro y ejecutarán en el orden en que se agregaron.
Esto podría ser útil si tiene varias razones por las que puede ocurrir una cancelación, lo que hace que la lógica de un solo controlador RunWorkerCompleted sea más complicada de lo que desea. Por ejemplo, cancelar cuando un usuario intenta cerrar el formulario:
fuente
Uso el
async
método yawait
espero a que el trabajador termine su trabajo:y en
DoWork
método:También puede encapsular el
while
bucleDoWork
contry ... catch
para establecer_isBusy
es unafalse
excepción. O simplemente verifique_worker.IsBusy
elStopAsync
ciclo while.Aquí hay un ejemplo de implementación completa:
Para detener al trabajador y esperar a que se ejecute hasta el final:
Los problemas con este método son:
fuente
oh hombre, algunos de estos se han vuelto ridículamente complejos. todo lo que necesita hacer es verificar la propiedad BackgroundWorker.CancellationPending dentro del controlador DoWork. Puedes consultarlo en cualquier momento. una vez que esté pendiente, establezca e.Cancel = True y salga del método.
// método aquí privado void Worker_DoWork (remitente del objeto, DoWorkEventArgs e) {BackgroundWorker bw = (remitente como BackgroundWorker);
}
fuente