Cómo detener BackgroundWorker correctamente

Tengo un formulario con 2 cuadros combinados en él. Y quiero llenar combobox2.DataSource basado en combobox1.Text y combobox2.Text (supongo que el usuario ha completado la entrada en combobox1 y está en el medio de ingresar en combobox2 ). Así que tengo un controlador de eventos para combobox2 como este:

 private void combobox2_TextChanged(object sender, EventArgs e) { if (cmbDataSourceExtractor.IsBusy) cmbDataSourceExtractor.CancelAsync(); var filledComboboxValues = new FilledComboboxValues{ V1 = combobox1.Text, V2 = combobox2.Text}; cmbDataSourceExtractor.RunWorkerAsync(filledComboboxValues ); } 

En cuanto a la construcción de DataSource es un proceso que consume mucho tiempo (crea una solicitud a la base de datos y la ejecuta), decidí que es mejor realizarla en otro proceso usando BackgroundWorker. Entonces, hay un escenario cuando cmbDataSourceExtractor no ha completado su trabajo y el usuario escribe un símbolo más. En este caso, recibo una excepción en esta línea
cmbDataSourceExtractor.RunWorkerAsync(filledComboboxValues ); acerca de que BackgroundWorker está ocupado y no puede realizar varias acciones al mismo tiempo.
¿Cómo deshacerse de esta excepción?
¡Gracias por adelantado!

CancelAsync realidad no aborta su hilo ni nada de eso. Envía un mensaje al hilo de trabajo que el trabajo debe cancelarse a través de BackgroundWorker.CancellationPending . Su delegado de DoWork que se está ejecutando en segundo plano debe verificar periódicamente esta propiedad y manejar la cancelación en sí.

La parte engañosa es que su delegado de DoWork probablemente esté bloqueando, lo que significa que el trabajo que realiza en su DataSource debe completarse antes de que pueda hacer algo más (como verificar Cancelación de Cancelación). Es posible que deba trasladar su trabajo real a otro delegado asíncrono (o mejor aún, enviar el trabajo a ThreadPool ), y tener su encuesta principal de subprocesos de trabajo hasta que este subproceso de trabajo interno active un estado de espera, O detecte Cancelación de cancelación.

http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.cancelasync.aspx

http://www.codeproject.com/KB/cpp/BackgroundWorker_Threads.aspx

Si agrega un bucle entre el CancelAsync () y el RunWorkerAsync () como así, resolverá su problema

  private void combobox2_TextChanged(object sender, EventArgs e) { if (cmbDataSourceExtractor.IsBusy) cmbDataSourceExtractor.CancelAsync(); while(cmbDataSourceExtractor.IsBusy) Application.DoEvents(); var filledComboboxValues = new FilledComboboxValues{ V1 = combobox1.Text, V2 = combobox2.Text}; cmbDataSourceExtractor.RunWorkerAsync(filledComboboxValues ); } 

El ciclo while con la llamada a Application.DoEvents () abrirá la ejecución de su nuevo hilo de trabajo hasta que el actual se haya cancelado correctamente, tenga en cuenta que aún necesita gestionar la cancelación de su hilo de trabajo. Con algo como:

  private void cmbDataSourceExtractor_DoWork(object sender, DoWorkEventArgs e) { if (this.cmbDataSourceExtractor.CancellationPending) { e.Cancel = true; return; } // do stuff... } 

Application.DoEvents () en el primer fragmento de código continuará procesando la cola de mensajes de subprocesos de GUI para que incluso se pueda procesar la cancelación de la propiedad cmbDataSourceExtractor.IsBusy (si simplemente agregó un continue en lugar de Application.DoEvents () el bucle bloquearía el hilo de la GUI en un estado ocupado y no procesaría el evento para actualizar el cmbDataSourceExtractor.IsBusy)

Deberá usar un indicador compartido entre el hilo principal y el BackgroundWorker, como BackgroundWorker.CancellationPending . Cuando desee que BackgroundWorker salga, simplemente configure el indicador utilizando BackgroundWorker.CancelAsync ().

MSDN tiene un ejemplo: http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.cancellationpending.aspx

MI ejemplo. DoWork está abajo:

  DoLengthyWork(); //this is never executed if(bgWorker.CancellationPending) { MessageBox.Show("Up to here? ..."); e.Cancel = true; } 

dentro de DoLenghtyWork:

 public void DoLenghtyWork() { OtherStuff(); for(int i=0 ; i<10000000; i++) { int j = i/3; } } 

dentro de OtherStuff ():

 public void OtherStuff() { for(int i=0 ; i<10000000; i++) { int j = i/3; } } 

Lo que quiere hacer es modificar DoLenghtyWork y OtherStuff () para que se conviertan en:

 public void DoLenghtyWork() { if(!bgWorker.CancellationPending) { OtherStuff(); for(int i=0 ; i<10000000; i++) { int j = i/3; } } } public void OtherStuff() { if(!bgWorker.CancellationPending) { for(int i=0 ; i<10000000; i++) { int j = i/3; } } } 

El problema es causado por el hecho de que cmbDataSourceExtractor.CancelAsync() es un método asincrónico, la operación Cancel aún no se ha completado cuando cmdDataSourceExtractor.RunWorkerAsync(...) exitst. Debería esperar a que cmdDataSourceExtractor complete antes de RunWorkerAsync llamar a RunWorkerAsync . Cómo hacer esto se explica en esta pregunta SO .

Mi respuesta es un poco diferente porque probé estos métodos pero no funcionaron. Mi código utiliza una clase extra que busca un indicador booleano en una clase pública estática a medida que se leen los valores de la base de datos o donde lo prefiero justo antes de agregar un objeto a un objeto de la Lista o algo así. Vea el cambio en el código a continuación. Agregué la propiedad ThreadWatcher.StopThread. para esta explicación, voy a restablecer el hilo actual porque no es su problema, pero eso es tan fácil como establecer la propiedad en falso antes de acceder al siguiente hilo …

 private void combobox2_TextChanged(object sender, EventArgs e) { //Stop the thread here with this ThreadWatcher.StopThread = true;//the rest of this thread will run normally after the database function has stopped. if (cmbDataSourceExtractor.IsBusy) cmbDataSourceExtractor.CancelAsync(); while(cmbDataSourceExtractor.IsBusy) Application.DoEvents(); var filledComboboxValues = new FilledComboboxValues{ V1 = combobox1.Text, V2 = combobox2.Text}; cmbDataSourceExtractor.RunWorkerAsync(filledComboboxValues ); } 

todo muy bien

 private void cmbDataSourceExtractor_DoWork(object sender, DoWorkEventArgs e) { if (this.cmbDataSourceExtractor.CancellationPending) { e.Cancel = true; return; } // do stuff... } 

Ahora agrega la siguiente clase

 public static class ThreadWatcher { public static bool StopThread { get; set; } } 

y en tu clase donde lees la base de datos

 Listlist = new List(); ... if (!reader.IsDbNull(0)) something = reader.getString(0); someobject = new someobject(something); if (ThreadWatcher.StopThread == true) break; list.Add(something); ... 

no te olvides de utilizar un bloque finally para cerrar correctamente la conexión de tu base de datos, etc. ¡Espero que esto ayude! Por favor, márcame si lo encuentras útil.

Estoy de acuerdo con chicos. Pero a veces tienes que agregar más cosas.

ES DECIR

1) Agregue este worker.WorkerSupportsCancellation = true;

2) Agregue a su clase algún método para hacer las siguientes cosas

 public void KillMe() { worker.CancelAsync(); worker.Dispose(); worker = null; GC.Collect(); } 

Entonces, antes de cerrar su aplicación, debe llamar a este método.

3) Probablemente puede Dispose, null todas las variables y temporizadores que están dentro del BackgroundWorker .

En mi caso, tuve que poner en común la base de datos para que se confirmara el pago y luego actualizar la interfaz de usuario de WPF .

Mecanismo que hace girar todos los procesos:

 public void Execute(object parameter) { try { var amount = ViewModel.Amount; var transactionId = ViewModel.TransactionMain.TransactionId.ToString(); var productCode = ViewModel.TransactionMain.TransactionDetailList.First().Product.ProductCode; var transactionReference = GetToken(amount, transactionId, productCode); var url = string.Format("{0}New?transactionReference={1}", Settings.Default.PaymentUrlWebsite, transactionReference); Process.Start(new ProcessStartInfo(url)); ViewModel.UpdateUiWhenDoneWithPayment = new BackgroundWorker {WorkerSupportsCancellation = true}; ViewModel.UpdateUiWhenDoneWithPayment.DoWork += ViewModel.updateUiWhenDoneWithPayment_DoWork; ViewModel.UpdateUiWhenDoneWithPayment.RunWorkerCompleted += ViewModel.updateUiWhenDoneWithPayment_RunWorkerCompleted; ViewModel.UpdateUiWhenDoneWithPayment.RunWorkerAsync(); } catch (Exception e) { ViewModel.Log.Error("Failed to navigate to payments", e); MessageBox.Show("Failed to navigate to payments"); } } 

Mecanismo que verifica la finalización:

  private void updateUiWhenDoneWithPayment_DoWork(object sender, DoWorkEventArgs e) { Thread.Sleep(30000); while (string.IsNullOrEmpty(GetAuthToken()) && !((BackgroundWorker)sender).CancellationPending) { Thread.Sleep(5000); } //Plug in pooling mechanism this.AuthCode = GetAuthToken(); } 

Mecanismo que cancela si la ventana se cierra:

 private void PaymentView_OnUnloaded(object sender, RoutedEventArgs e) { var context = DataContext as PaymentViewModel; if (context.UpdateUiWhenDoneWithPayment != null && context.UpdateUiWhenDoneWithPayment.WorkerSupportsCancellation && context.UpdateUiWhenDoneWithPayment.IsBusy) context.UpdateUiWhenDoneWithPayment.CancelAsync(); }