¿Cómo esperar a que un BackgroundWorker se cancele?


110

Considérese una hipotética método de un objeto que hace cosas para 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 un BackgroundWorker que hacer?


En el pasado las personas han tratado:

while (_worker.IsBusy) 
{ 
    Sleep(100); 
} 

Pero this deadlocks, porque IsBusy no se borra hasta después de que se manejó el caso RunWorkerCompleted, y ese evento no puede conseguir manejado hasta que la aplicación entra en inactividad . La aplicación no estará inactiva hasta que el trabajador termine. (Además, es un ajetreado bucle - asqueroso.)

Otros han agregue sugerido kludging en:

while (_worker.IsBusy) 
{ 
    Application.DoEvents(); 
} 

El problema con esto es que es Application.DoEvents() hace que los mensajes actualmente en la cola para ser procesados, lo que causa problemas de reentrada (.NET no es reentrante).

espero utilizar alguna solución que involucra objetos de sincronización de eventos, donde el código espera para un evento - que los manejadores de eventos conjuntos RunWorkerCompleted del trabajador. Algo así como:

Event _workerDoneEvent = new WaitHandle(); 

public void CancelDoingStuff() 
{ 
    _worker.CancelAsync(); 
    _workerDoneEvent.WaitOne(); 
} 

private void RunWorkerCompletedEventHandler(sender object, RunWorkerCompletedEventArgs e) 
{ 
    _workerDoneEvent.SetEvent(); 
} 

Pero estoy de vuelta al punto muerto: el controlador de eventos no se puede ejecutar hasta que la aplicación entra en inactividad, y la aplicación no voy a entrar en reposo porque está a la espera de un evento.

Entonces, ¿cómo puedes esperar para que un BackgroundWorker termine?


actualización La gente parece estar confundido por esta pregunta. Parecen pensar que me va a utilizar el BackgroundWorker como:

BackgroundWorker worker = new BackgroundWorker(); 
worker.DoWork += MyWork; 
worker.RunWorkerAsync(); 
WaitForWorkerToFinish(worker); 

Eso es no ella, 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 segundo plano.

116

Si entiendo su derecha requisito, se podría hacer algo como esto (código no probado, pero muestra la idea general):

private BackgroundWorker worker = new BackgroundWorker(); 
private AutoResetEvent _resetEvent = new AutoResetEvent(false); 

public Form1() 
{ 
    InitializeComponent(); 

    worker.DoWork += worker_DoWork; 
} 

public void Cancel() 
{ 
    worker.CancelAsync(); 
    _resetEvent.WaitOne(); // will block until _resetEvent.Set() call made 
} 

void worker_DoWork(object sender, DoWorkEventArgs e) 
{ 
    while(!e.Cancel) 
    { 
     // do something 
    } 

    _resetEvent.Set(); // signal that worker is done 
} 
  0

Eso funciona muy bien, excepto por un error. El evento debe crearse en el estado "no establecido": privado AutoResetEvent _resetEvent = new AutoResetEvent (false); 23 sep. 082008-09-23 21:17:12

+1

Entonces, si pudieras editar tu publicación y hacer esa corrección, ¡sería una respuesta adecuada para todas las edades! 23 sep. 082008-09-23 21:17:50

  0

Buena captura; corregido :) 24 sep. 082008-09-24 07:59:50

+7

Esto bloqueará la interfaz de usuario (por ejemplo, no se volverán a pintar) si el técnico de segundo plano tarda mucho tiempo en cancelarla. Es mejor usar uno de los siguientes para detener la interacción con la IU: http://stackoverflow.com/questions/123661/net-how-to-wait-for-a-backgroundworker-to-complete#126570 24 sep. 082008-09-24 11:26:09

+1

+1 just lo que ordenó el médico ... aunque estoy de acuerdo con @Joe si la solicitud de cancelación puede demorar más de un segundo. 30 jul. 092009-07-30 16:32:45

  0

¿Qué sucede cuando se controla el CancelAsync antes del WaitOne? ¿O es que el botón Cancelar solo funciona una vez? 01 feb. 112011-02-01 13:19:35

+3

Necesitaba comprobar el '((BackgroundWorker) remitente) .CancellationPending' para obtener el evento cancelar 06 mar. 132013-03-06 08:12:56

  0

¿No debería ser este un evento ManualReset? Hacer clic en la "x" dos veces podría congelar la interfaz de usuario 17 feb. 152015-02-17 14:41:29

  0

Según lo mencionado por Luuk, no es la propiedad Cancelar que debe marcarse, sino que canceló Cancelación. En segundo lugar, como menciona Joel Coehoorn, esperando que el hilo termine las derrotas con el propósito de usar un hilo en primer lugar. 06 oct. 172017-10-06 09:17:12


4

Puede comprobar en las RunWorkerCompletedEventArgs en el RunWorkerCompletedEventHandler para ver cuál es el estado era. Éxito, cancelado o un error

private void RunWorkerCompletedEventHandler(sender object, RunWorkerCompletedEventArgs e) 
{ 
    if(e.Cancelled) 
    { 
     Console.WriteLine("The worker was cancelled."); 
    } 
} 

actualización: Para ver si su trabajador ha llamado .CancelAsync() utilizando la siguiente:

if (_worker.CancellationPending) 
{ 
    Console.WriteLine("Cancellation is pending, no need to call CancelAsync again"); 
} 
+1

El requisito es que CancelDoingStuff() no pueda regresar hasta que se complete el trabajador. Ver cómo se completó realmente no es de interés. 23 sep. 082008-09-23 20:38:32

  0

Luego tendrá que crear un evento. Realmente no tiene nada que ver con BackgroundWorker específicamente, solo necesitas implementar un evento, escucharlo y disparar cuando hayas terminado. Y RunWorkerCompletedEventHandler es cuando está hecho. Dispara otro evento. 23 sep. 082008-09-23 21:03:01


3

¿Por qué no acaba de atar en el BackgroundWorker.RunWorkerCompleted Evento. Es una devolución de llamada que "ocurrirá cuando la operación en segundo plano se haya completado, se haya cancelado o haya generado una excepción."

  0

Porque la persona que está utilizando el objeto DoesStuff ha pedido que se cancele. Los recursos compartidos que utiliza el objeto están a punto de desconectarse, eliminarse, eliminarse, cerrarse, y necesita saber que está hecho para poder continuar. 23 sep. 082008-09-23 20:47:55


1

No entiendo por qué te gustaría que esperar un BackgroundWorker para completar;. Realmente parece exactamente lo contrario de la motivación para la clase

Sin embargo, usted podría comenzar cada método con una llamar a worker.IsBusy y hacer que la salida si se está ejecutando.

  0

Mala elección de palabras; No estoy esperando que se complete. 24 sep. 082008-09-24 13:44:58


0

Hm tal vez yo no estoy recibiendo su pregunta correcta.

el BackgroundWorker llama al evento WorkerCompleted vez su 'workermethod' (el método/función/sub maneja el backgroundworker.doWork-event) ha finalizado, por lo que no es necesario verificar si el BW todavía está funcionando. Si desea detener a su trabajador, marque el cancellation pending property dentro de su 'método de trabajador'.

  0

entiendo que el trabajador tiene que controlar la propiedad CancellationPending y salir tan pronto como sea posible. Pero, ¿cómo puede la persona que está afuera, que ha solicitado la cancelación del trabajador de fondo, esperar a que se haga? 23 sep. 082008-09-23 20:52:44

  0

El evento WorkCompleted todavía se activará. 23 sep. 082008-09-23 20:58:26

  0

"¿Pero cómo la persona que está afuera, que ha solicitado la cancelación del trabajador de segundo plano, espera a que se haga?" 24 sep. 082008-09-24 13:45:55

  0

Espera a que se realice esperando a que se active el evento WorkCompleted. Si desea evitar que el usuario haga clic en su GUI, puede usar una de las soluciones propuestas por @Joe en su [respuesta anterior] (http://stackoverflow.com/a/126570/55487) (deshabilitar el formulario, o mostrar algo modal). En otras palabras, permita que el ciclo inactivo del sistema lo espere; le dirá cuándo ha terminado (activando el evento). 07 mar. 132013-03-07 17:07:31


4

Usted no espere a que se complete el trabajador en segundo plano. Eso prácticamente frustra el propósito de lanzar un hilo por separado. En su lugar, debe dejar que su método finalice, y mover cualquier código que depende de la finalización a un lugar diferente. Deje que el trabajador le diga cuándo termina y llame a cualquier código restante.

Si desea espere para algo para completar utilice una construcción de subprocesamiento diferente que proporciona un WaitHandle.

+1

¿Puede sugerir uno? Un trabajador de fondo parece ser la única construcción de subprocesos que puede enviar notificaciones al subproceso que creó el objeto BackgroundWorker. 23 sep. 082008-09-23 21:01:10

  0

El backgroundworker es una construcción _UI_. Solo utiliza eventos que tu propio código aún necesita saber cómo llamar. Nada le impide crear sus propios delegados para eso. 23 sep. 082008-09-23 22:00:27


0

El flujo de trabajo de un objeto BackgroundWorker básicamente se requiere para controlar el evento RunWorkerCompleted tanto para la ejecución normal como para los casos de uso de cancelación. Es por esto que existe la propiedad RunWorkerCompletedEventArgs.Cancelled. Básicamente, hacer esto correctamente requiere que consideres que tu método Cancelar es un método asíncrono en sí mismo.

He aquí un ejemplo:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Windows.Forms; 
using System.ComponentModel; 

namespace WindowsFormsApplication1 
{ 
    public class AsyncForm : Form 
    { 
     private Button _startButton; 
     private Label _statusLabel; 
     private Button _stopButton; 
     private MyWorker _worker; 

     public AsyncForm() 
     { 
      var layoutPanel = new TableLayoutPanel(); 
      layoutPanel.Dock = DockStyle.Fill; 
      layoutPanel.ColumnStyles.Add(new ColumnStyle()); 
      layoutPanel.ColumnStyles.Add(new ColumnStyle()); 
      layoutPanel.RowStyles.Add(new RowStyle(SizeType.AutoSize)); 
      layoutPanel.RowStyles.Add(new RowStyle(SizeType.Percent, 100)); 

      _statusLabel = new Label(); 
      _statusLabel.Text = "Idle."; 
      layoutPanel.Controls.Add(_statusLabel, 0, 0); 

      _startButton = new Button(); 
      _startButton.Text = "Start"; 
      _startButton.Click += HandleStartButton; 
      layoutPanel.Controls.Add(_startButton, 0, 1); 

      _stopButton = new Button(); 
      _stopButton.Enabled = false; 
      _stopButton.Text = "Stop"; 
      _stopButton.Click += HandleStopButton; 
      layoutPanel.Controls.Add(_stopButton, 1, 1); 

      this.Controls.Add(layoutPanel); 
     } 

     private void HandleStartButton(object sender, EventArgs e) 
     { 
      _stopButton.Enabled = true; 
      _startButton.Enabled = false; 

      _worker = new MyWorker() { WorkerSupportsCancellation = true }; 
      _worker.RunWorkerCompleted += HandleWorkerCompleted; 
      _worker.RunWorkerAsync(); 

      _statusLabel.Text = "Running..."; 
     } 

     private void HandleStopButton(object sender, EventArgs e) 
     { 
      _worker.CancelAsync(); 
      _statusLabel.Text = "Cancelling..."; 
     } 

     private void HandleWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 
     { 
      if (e.Cancelled) 
      { 
       _statusLabel.Text = "Cancelled!"; 
      } 
      else 
      { 
       _statusLabel.Text = "Completed."; 
      } 

      _stopButton.Enabled = false; 
      _startButton.Enabled = true; 
     } 

    } 

    public class MyWorker : BackgroundWorker 
    { 
     protected override void OnDoWork(DoWorkEventArgs e) 
     { 
      base.OnDoWork(e); 

      for (int i = 0; i < 10; i++) 
      { 
       System.Threading.Thread.Sleep(500); 

       if (this.CancellationPending) 
       { 
        e.Cancel = true; 
        e.Result = false; 
        return; 
       } 
      } 

      e.Result = true; 
     } 
    } 
} 

Si realmente no quiere que su método para salir, me gustaría sugerir poner una bandera como un AutoResetEvent en un derivado BackgroundWorker, a continuación, anular OnRunWorkerCompleted para establecer la bandera. Sin embargo, todavía es un poco kludgy; Recomiendo tratar el evento cancelar como un método asincrónico y hacer lo que esté haciendo actualmente en el controlador RunWorkerCompleted.

  0

Mover el código a RunWorkerCompleted no está donde pertenece, y no es bonito. 24 sep. 082008-09-24 13:47:00


13

Hay un problema con la respuesta this. La IU debe continuar procesando los mensajes mientras espera; de lo contrario, no se volverá a pintar, lo que será un problema si el técnico de soporte tarda mucho tiempo en responder a la solicitud de cancelación.

Una segunda falla es que _resetEvent.Set() nunca se invocará si el hilo del trabajador arroja una excepción, dejando el hilo principal esperando indefinidamente, sin embargo, este defecto podría solucionarse fácilmente con un bloque try/finally.

Una forma de hacer esto es mostrar un cuadro de diálogo modal que tiene un temporizador que comprueba repetidamente si el trabajador de fondo ha terminado el trabajo (o ha terminado de cancelar en su caso). Una vez que el trabajador de fondo ha terminado, el diálogo modal devuelve 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 como máximo una ventana no modal abierta) es establecer ActiveForm.Enabled = false, luego realizar un bucle en la aplicación, DoEvents hasta que el trabajador de fondo haya terminado de cancelar, después de lo cual puede establecer ActiveForm.Enabled = cierto de nuevo

+4

Eso puede ser un problema, pero se acepta fundamentalmente como parte de la pregunta, "Cómo ** ** esperar ** para que un BackgroundWorker se cancele". Esperar significa que esperas, no haces nada más. Esto también incluye procesar mensajes. Si no quería esperar al segundo usuario, simplemente podría llamar a .CancelAsync. Pero ese no es el requisito de diseño aquí. 11 jul. 092009-07-11 14:07:52

+1

+1 para indicar el valor del método CancelAsync, versos * en espera * para el trabajador en segundo plano. 11 jul. 092009-07-11 14:10:04


10

Casi todos ustedes están confundidos por la pregunta, y no están entendiendo cómo se usa un trabajador.

Considere un controlador de eventos RunWorkerComplete:

private void OnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 
{ 
    if (!e.Cancelled) 
    { 
     rocketOnPad = false; 
     label1.Text = "Rocket launch complete."; 
    } 
    else 
    { 
     rocketOnPad = true; 
     label1.Text = "Rocket launch aborted."; 
    } 
    worker = null; 
} 

Y todo es bueno.

Ahora llega una situación donde la persona que llama debe abortar la cuenta atrás porque necesitan ejecutar una autodestrucción de emergencia del cohete.

private void BlowUpRocket() 
{ 
    if (worker != null) 
    { 
     worker.CancelAsync(); 
     WaitForWorkerToFinish(worker); 
     worker = null; 
    } 

    StartClaxon(); 
    SelfDestruct(); 
} 

Y también hay una situación en la que tenemos que abrir las puertas de acceso al cohete, pero no mientras se hace una cuenta regresiva:

private void OpenAccessGates() 
{ 
    if (worker != null) 
    { 
     worker.CancelAsync(); 
     WaitForWorkerToFinish(worker); 
     worker = null; 
    } 

    if (!rocketOnPad) 
     DisengageAllGateLatches(); 
} 

Y, por último, hay que des-combustible del cohete , pero eso no está permitido durante una cuenta regresiva:

private void DrainRocket() 
{ 
    if (worker != null) 
    { 
     worker.CancelAsync(); 
     WaitForWorkerToFinish(worker); 
     worker = null; 
    } 

    if (rocketOnPad) 
     OpenFuelValves(); 
} 

sin la capacidad de esperar a que un trabajador de cancelar, hay que mover los tres métodos para la RunWorkerCompletedEvent:

private void OnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 
{ 
    if (!e.Cancelled) 
    { 
     rocketOnPad = false; 
     label1.Text = "Rocket launch complete."; 
    } 
    else 
    { 
     rocketOnPad = true; 
     label1.Text = "Rocket launch aborted."; 
    } 
    worker = null; 

    if (delayedBlowUpRocket) 
     BlowUpRocket(); 
    else if (delayedOpenAccessGates) 
     OpenAccessGates(); 
    else if (delayedDrainRocket) 
     DrainRocket(); 
} 

private void BlowUpRocket() 
{ 
    if (worker != null) 
    { 
     delayedBlowUpRocket = true; 
     worker.CancelAsync(); 
     return; 
    } 

    StartClaxon(); 
    SelfDestruct(); 
} 

private void OpenAccessGates() 
{ 
    if (worker != null) 
    { 
     delayedOpenAccessGates = true; 
     worker.CancelAsync(); 
     return; 
    } 

    if (!rocketOnPad) 
     DisengageAllGateLatches(); 
} 

private void DrainRocket() 
{ 
    if (worker != null) 
    { 
     delayedDrainRocket = true; 
     worker.CancelAsync(); 
     return; 
    } 

    if (rocketOnPad) 
     OpenFuelValves(); 
} 

Ahora podría escribir mi código así, pero no voy a hacerlo. No me importa, simplemente no lo estoy.

+11

¿Dónde está el método WaitForWorkerToFinish? cualquier código fuente completo? 11 jul. 122012-07-11 11:29:32


-2

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 verificarlo en cualquier momento. una vez que esté pendiente, configure e.Cancel = True y libere del método.

// método aquí privada Worker_DoWork void (object sender, DoWorkEventArgs e) { BackgroundWorker BW = (remitente como BackgroundWorker);

// do stuff 

if(bw.CancellationPending) 
{ 
    e.Cancel = True; 
    return; 
} 

// do other stuff 

}

+1

Y con esta solución, ¿cómo es que la persona que llamó a CancelAsync se vio obligada a esperar que el trabajador de fondo se cancele? 06 oct. 082008-10-06 15:58:53


0

estoy un poco tarde a la fiesta aquí (aproximadamente 4 años), pero ¿qué pasa con la creación de un subproceso asincrónico que se puede manejar un bucle ocupado sin bloquear la interfaz de usuario, y luego tener la devolución de llamada desde ese hilo, ¿será la confirmación de que BackgroundWorker ha terminado de cancelar?

Algo como esto:

class Test : Form 
{ 
    private BackgroundWorker MyWorker = new BackgroundWorker(); 

    public Test() { 
     MyWorker.DoWork += new DoWorkEventHandler(MyWorker_DoWork); 
    } 

    void MyWorker_DoWork(object sender, DoWorkEventArgs e) { 
     for (int i = 0; i < 100; i++) { 
      //Do stuff here 
      System.Threading.Thread.Sleep((new Random()).Next(0, 1000)); //WARN: Artificial latency here 
      if (MyWorker.CancellationPending) { return; } //Bail out if MyWorker is cancelled 
     } 
    } 

    public void CancelWorker() { 
     if (MyWorker != null && MyWorker.IsBusy) { 
      MyWorker.CancelAsync(); 
      System.Threading.ThreadStart WaitThread = new System.Threading.ThreadStart(delegate() { 
       while (MyWorker.IsBusy) { 
        System.Threading.Thread.Sleep(100); 
       } 
      }); 
      WaitThread.BeginInvoke(a => { 
       Invoke((MethodInvoker)delegate() { //Invoke your StuffAfterCancellation call back onto the UI thread 
        StuffAfterCancellation(); 
       }); 
      }, null); 
     } else { 
      StuffAfterCancellation(); 
     } 
    } 

    private void StuffAfterCancellation() { 
     //Things to do after MyWorker is cancelled 
    } 
} 

En esencia, lo que hace es disparar otro hilo para funcionar en el fondo que sólo espera en su bucle ocupado para ver si el MyWorker ha completado. Una vez que haya finalizado la cancelación del MyWorker, el hilo saldrá y podemos usarlo como AsyncCallback para ejecutar cualquier método que necesitemos para seguir la cancelación exitosa; funcionará como un evento psuedo. Como esto está separado del subproceso UI, no bloqueará la UI mientras esperamos que MyWorker termine de cancelarse. Si tu intención es bloquear y esperar la cancelación, entonces esto no sirve para nada, pero si solo quieres esperar para poder comenzar otro proceso, esto funciona muy bien.


0
Imports System.Net 
Imports System.IO 
Imports System.Text 

Public Class Form1 
    Dim f As New Windows.Forms.Form 
    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click 
    BackgroundWorker1.WorkerReportsProgress = True 
    BackgroundWorker1.RunWorkerAsync() 
    Dim l As New Label 
    l.Text = "Please Wait" 
    f.Controls.Add(l) 
    l.Dock = DockStyle.Fill 
    f.StartPosition = FormStartPosition.CenterScreen 
    f.FormBorderStyle = Windows.Forms.FormBorderStyle.None 
    While BackgroundWorker1.IsBusy 
     f.ShowDialog() 
    End While 
End Sub 




Private Sub BackgroundWorker1_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork 

    Dim i As Integer 
    For i = 1 To 5 
     Threading.Thread.Sleep(5000) 
     BackgroundWorker1.ReportProgress((i/5) * 100) 
    Next 
End Sub 

Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged 
    Me.Text = e.ProgressPercentage 

End Sub 

Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted 

    f.Close() 

End Sub 

End Class 
  0

¿Cuál es tu pregunta? En SO, debe ser específico al hacer las preguntas 28 ago. 132013-08-28 10:38:24

  0

@ Esto no es una pregunta. 28 ago. 132013-08-28 10:39:49


0

Sé que esto es muy tarde (5 años), pero lo que busca es utilizar un hilo y una SynchronizationContext. Tendrá que coordinar las llamadas de IU a la secuencia de la interfaz de usuario "a mano" en lugar de dejar que el Framework lo haga de forma automática.

Esto le permite utilizar un hilo que puede esperar si es necesario.


0

La solución de Fredrik Kalseth a este problema es la mejor que he encontrado hasta ahora. Otras soluciones usan Application.DoEvent() que pueden causar problemas o simplemente no funcionan. Déjame convertir su solución en una clase reutilizable. Desde BackgroundWorker no está sellado, podemos derivar la clase de ella:

public class BackgroundWorkerEx : BackgroundWorker 
{ 
    private AutoResetEvent _resetEvent = new AutoResetEvent(false); 
    private bool _resetting, _started; 
    private object _lockObject = new object(); 

    public void CancelSync() 
    { 
     bool doReset = false; 
     lock (_lockObject) { 
      if (_started && !_resetting) { 
       _resetting = true; 
       doReset = true; 
      } 
     } 
     if (doReset) { 
      CancelAsync(); 
      _resetEvent.WaitOne(); 
      lock (_lockObject) { 
       _started = false; 
       _resetting = false; 
      } 
     } 
    } 

    protected override void OnDoWork(DoWorkEventArgs e) 
    { 
     lock (_lockObject) { 
      _resetting = false; 
      _started = true; 
      _resetEvent.Reset(); 
     } 
     try { 
      base.OnDoWork(e); 
     } finally { 
      _resetEvent.Set(); 
     } 
    } 
} 

Con banderas y bloqueo adecuado, nos aseguramos de que _resetEvent.WaitOne() realmente sólo se llama a si un trabajo se ha iniciado, de lo contrario _resetEvent.Set(); nunca se podría sido llamados!

El try-finally asegura que se llamará a _resetEvent.Set();, incluso si se produce una excepción en nuestro DoWork-handler. De lo contrario, la aplicación podría congelarse para siempre al llamar al CancelSync.

Nos gustaría utilizar de esta manera:

BackgroundWorkerEx _worker; 

void StartWork() 
{ 
    StopWork(); 
    _worker = new BackgroundWorkerEx { 
     WorkerSupportsCancellation = true, 
     WorkerReportsProgress = true 
    }; 
    _worker.DoWork += Worker_DoWork; 
    _worker.ProgressChanged += Worker_ProgressChanged; 
} 

void StopWork() 
{ 
    if (_worker != null) { 
     _worker.CancelSync(); // Use our new method. 
    } 
} 

private void Worker_DoWork(object sender, DoWorkEventArgs e) 
{ 
    for (int i = 1; i <= 20; i++) { 
     if (worker.CancellationPending) { 
      e.Cancel = true; 
      break; 
     } else { 
      // Simulate a time consuming operation. 
      System.Threading.Thread.Sleep(500); 
      worker.ReportProgress(5 * i); 
     } 
    } 
} 

private void Worker_ProgressChanged(object sender, ProgressChangedEventArgs e) 
{ 
    progressLabel.Text = e.ProgressPercentage.ToString() + "%"; 
} 

También puede agregar un controlador para el evento RunWorkerCompleted como se muestra aquí:
          BackgroundWorker Class(documentación de Microsoft).


1

sólo quiero decir que vine aquí porque necesito un trabajador de fondo a esperar mientras corría un proceso asíncrono, mientras que en un bucle, mi solución era mucho más fácil que todas estas otras cosas ^^

foreach(DataRow rw in dt.Rows) 
{ 
    //loop code 
    while(!backgroundWorker1.IsBusy) 
    { 
     backgroundWorker1.RunWorkerAsync(); 
    } 
} 

Justo pensé que compartiría porque aquí es donde terminé mientras buscaba una solución. Además, esta es mi primera publicación sobre el desbordamiento de la pila, así que si es malo o algo, ¡me encantaría la crítica! :)


0

Al cerrar el formulario, se cierra mi archivo de registro abierto. Mi trabajador de fondo escribe ese archivo de registro, así que no puedo dejar que MainWin_FormClosing() finalice hasta que mi trabajador de fondo termine. Si no espero a que finalice mi trabajador de segundo plano, suceden excepciones.

¿Por qué es esto 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 inmediatamente después de que finalice el trabajador de fondo, simplemente use una variable. Esto funciona para mí:

private volatile bool bwRunning = false; 

... 

private void MainWin_FormClosing(Object sender, FormClosingEventArgs e) 
{ 
    ... // Clean house as-needed. 

    bwInstance.CancelAsync(); // Flag background worker to stop. 
    while (bwRunning) 
     Thread.Sleep(100); // Wait for background worker to stop. 
} // (The form really gets closed now.) 

... 

private void bwBody(object sender, DoWorkEventArgs e) 
{ 
    bwRunning = true; 

    BackgroundWorker bw = sender as BackgroundWorker; 

    ... // Set up (open logfile, etc.) 

    for (; ;) // infinite loop 
    { 
     ... 
     if (bw.CancellationPending) break; 
     ... 
    } 

    ... // Tear down (close logfile, etc.) 

    bwRunning = false; 
} // (bwInstance dies now.) 

0

Puede realizar una copia de seguridad del evento RunWorkerCompleted. Incluso si ya ha agregado un controlador de eventos para _worker, puede agregar otro y se ejecutarán en el orden en que se agregaron.

public class DoesStuff 
{ 
    BackgroundWorker _worker = new BackgroundWorker(); 

    ... 

    public void CancelDoingStuff() 
    { 
     _worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler((sender, e) => 
     { 
      // do whatever you want to do when the cancel completes in here! 
     }); 
     _worker.CancelAsync(); 
    } 
} 

esto podría ser útil si tiene varias razones por las que una cancelación puede ocurrir, por lo que la lógica de un único controlador RunWorkerCompleted más complicado de lo que desea. Por ejemplo, cancelar cuando un usuario intenta cerrar el formulario:

void Form1_FormClosing(object sender, FormClosingEventArgs e) 
{ 
    if (_worker != null) 
    { 
     _worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler((sender3, e3) => this.Close()); 
     _worker.CancelAsync(); 
     e.Cancel = true; 
    } 
}