.net multithreading backgroundworker

.net - ¿Cómo esperar correctamente hasta que se complete BackgroundWorker?



multithreading (10)

Observe el siguiente código:

var handler = GetTheRightHandler(); var bw = new BackgroundWorker(); bw.RunWorkerCompleted += OnAsyncOperationCompleted; bw.DoWork += OnDoWorkLoadChildren; bw.RunWorkerAsync(handler);

Ahora supongamos que quiero esperar hasta que bw termine de funcionar. ¿Cuál es la forma correcta de hacerlo?

Mi solución es la siguiente:

bool finished = false; var handler = GetTheRightHandler(); var bw = new BackgroundWorker(); bw.RunWorkerCompleted += (sender, args) => { OnAsyncOperationCompleted(sender, args); finished = true; }); bw.DoWork += OnDoWorkLoadChildren; bw.RunWorkerAsync(handler); int timeout = N; while (!finished && timeout > 0) { Thread.Sleep(1000); --timeout; } if (!finished) { throw new TimedoutException("bla bla bla"); }

Pero no me gusta.

He considerado reemplazar el indicador finished con un evento de sincronización, configurarlo en el controlador RunWorkerCompleted y bloquearlo más tarde en lugar de hacer el bucle while-sleep.

Lamentablemente, está mal, porque el código puede ejecutarse en el contexto de sincronización de WPF o WindowsForm, en cuyo caso bloquearía el mismo hilo en el que se RunWorkerCompleted controlador RunWorkerCompleted , lo que claramente no es un movimiento muy inteligente.

Me gustaría saber de una mejor solución.

Gracias.

EDITAR:

PD

  • El código de muestra está diseñado de manera intencional para aclarar mi pregunta. Soy perfectamente consciente de la devolución de llamada de finalización y, sin embargo, quiero saber cómo esperar hasta que finalice. Esa es mi pregunta.
  • Soy consciente de Thread.Join , Delegate.BeginInvoke , ThreadPool.QueueUserWorkItem , etc ... La pregunta es específicamente acerca de BackgroundWorker .

EDIT 2:

OK, supongo que será mucho más fácil si explico el escenario.

Tengo un método de prueba de unidad, que invoca un código asíncrono, que a su vez en última instancia contrata un BackgroundWorker al que puedo pasar un controlador de finalización. Todo el código es mío, así que puedo cambiar la implementación si así lo deseo. Sin embargo, no voy a reemplazar el BackgroundWorker , ya que utiliza automáticamente el contexto de sincronización correcto, de modo que cuando se invoca el código en un subproceso de la interfaz de usuario se invoca la devolución de llamada de finalización en el mismo subproceso de la interfaz de usuario, lo cual es muy bueno.

De todos modos, es posible que el método de prueba de unidad llegue al final antes de que BW termine su trabajo, lo cual no es bueno. Así que deseo esperar hasta que se complete el BW y me gustaría saber la mejor manera de hacerlo.

Hay más piezas, pero el panorama general es más o menos como el que acabo de describir.


VB.NET

While BackgroundWorker1.IsBusy() Windows.Forms.Application.DoEvents() End While

Puedes usar esto para encadenar múltiples eventos. (código sudo para seguir)

download_file("filepath") While BackgroundWorker1.IsBusy() Windows.Forms.Application.DoEvents() End While ''Waits to install until the download is complete and lets other UI events function install_file("filepath") While BackgroundWorker1.IsBusy() Windows.Forms.Application.DoEvents() End While ''Waits for the install to complete before presenting the message box msgbox("File Installed")


BackgroundWorker tiene un evento de finalización. En lugar de esperar, llame a su ruta de código restante desde el controlador de finalización.


En OpenCV existe la función WaitKey. Ir permite resolver este problema de esa manera:

while (this->backgroundWorker1->IsBusy) { waitKey(10); std::cout << "Wait for background process: " << std::endl; } this->backgroundWorker1->RunWorkerAsync();


Esta pregunta es antigua, pero no creo que el autor haya recibido la respuesta que estaba buscando.

Esto está un poco sucio, y está en vb.NET pero funciona para mí

Private Sub MultiTaskingForThePoor() Try ''Start background worker bgwAsyncTasks.RunWorkerAsync() ''Do some other stuff here For i as integer = 0 to 100 lblOutput.Text = cstr(i) Next ''Wait for Background worker While bgwAsyncTasks.isBusy() Windows.Forms.Application.DoEvents() End While ''Voila, we are back in sync lblOutput.Text = "Success!" Catch ex As Exception MsgBox("Oops!" & vbcrlf & ex.Message) End Try End Sub


Intenta usar la clase AutoResetEvent de esta manera:

var doneEvent = new AutoResetEvent(false); var bw = new BackgroundWorker(); bw.DoWork += (sender, e) => { try { if (!e.Cancel) { // Do work } } finally { doneEvent.Set(); } }; bw.RunWorkerAsync(); doneEvent.WaitOne();

Precaución: debe asegurarse de que doneEvent.Set() se llame sin importar lo que suceda. También es posible que desee proporcionar a doneEvent.WaitOne() un argumento que especifique un período de tiempo de espera.

Nota: Este código es prácticamente una copia de la respuesta de Fredrik Kalseth a una pregunta similar .


La comprobación de backgrWorker.IsBusy en el bucle con Application.DoEvents() no es una forma agradable.

Estoy de acuerdo con @JohannesH, debería usar AutoResetEvent como una solución elegante. Pero al no usarlo en el hilo de la interfaz de usuario, se bloqueará el hilo principal; debe venir de otro hilo de trabajador de fondo.

AutoResetEvent aevent = new AutoResetEvent(false); private void button1_Click(object sender, EventArgs e) { bws = new BackgroundWorker(); bws.DoWork += new DoWorkEventHandler(bw_work); bws.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_complete); bws.RunWorkerAsync(); bwWaiting.DoWork += new DoWorkEventHandler(waiting_work); bwWaiting.RunWorkerCompleted += new RunWorkerCompletedEventHandler(waiting_complete); bwWaiting.RunWorkerAsync(); } void bw_work(object sender, DoWorkEventArgs e) { Thread.Sleep(2000); } void bw_complete(object sender, RunWorkerCompletedEventArgs e) { Debug.WriteLine("complete " + bwThread.ToString()); aevent.Set(); } void waiting_work(object sender, DoWorkEventArgs e) { aevent.WaitOne(); } void waiting_complete(object sender, RunWorkerCompletedEventArgs e) { Debug.WriteLine("complete waiting thread"); }


No estoy seguro de lo que quieres decir con esperar. ¿Quiere decir que quiere que se haga algo (por BW) después de eso, quiere hacer otra cosa? Use bw.RunWorkerCompleted como lo hace usted (use una función separada para facilitar la lectura) y en esa función de devolución de llamada, haga lo siguiente. Iniciar un temporizador para comprobar si el trabajo no toma demasiado tiempo.

var handler = GetTheRightHandler(); var bw = new BackgroundWorker(); bw.RunWorkerCompleted += (sender, args) => { OnAsyncOperationCompleted(sender, args); }); bw.DoWork += OnDoWorkLoadChildren; bw.RunWorkerAsync(handler); Timer Clock=new Timer(); Clock.Interval=1000; Clock.Start(); Clock.Tick+=new EventHandler(Timer_Tick); public void Timer_Tick(object sender,EventArgs eArgs) { if (bw.WorkerSupportsCancellation == true) { bw.CancelAsync(); } throw new TimedoutException("bla bla bla"); }

En el OnDoWorkLoadChildren:

if ((worker.CancellationPending == true)) { e.Cancel = true; //return or something }


Para esperar un subproceso de trabajo en segundo plano (único o múltiple) haga lo siguiente:

  1. Cree una lista de trabajadores de fondo que haya creado programáticamente:

    private IList<BackgroundWorker> m_WorkersWithData = new List<BackgroundWorker>();

  2. Agregue el trabajador de fondo en la lista:

    BackgroundWorker worker = new BackgroundWorker(); worker.DoWork += new DoWorkEventHandler(worker_DoWork); worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged); worker.WorkerReportsProgress = true; m_WorkersWithData.Add(worker); worker.RunWorkerAsync();

  3. Use la siguiente función para esperar a todos los trabajadores en la Lista:

    private void CheckAllThreadsHaveFinishedWorking() { bool hasAllThreadsFinished = false; while (!hasAllThreadsFinished) { hasAllThreadsFinished = (from worker in m_WorkersWithData where worker.IsBusy select worker).ToList().Count == 0; Application.DoEvents(); //This call is very important if you want to have a progress bar and want to update it //from the Progress event of the background worker. Thread.Sleep(1000); //This call waits if the loop continues making sure that the CPU time gets freed before //re-checking. } m_WorkersWithData.Clear(); //After the loop exits clear the list of all background workers to release memory. //On the contrary you can also dispose your background workers. }


También estaba buscando una solución adecuada. Resolví la espera con un candado exclusivo. La ruta crítica en el código es escribir en un contenedor público (aquí, la consola) y aumentar o disminuir los trabajadores. Ningún hilo debe interferir al escribir en esta variable, de lo contrario, el recuento ya no está garantizado.

public class Program { public static int worker = 0; public static object lockObject = 0; static void Main(string[] args) { BackgroundworkerTest backgroundworkerTest = new BackgroundworkerTest(); backgroundworkerTest.WalkDir("C://"); while (backgroundworkerTest.Worker > 0) { // Exclusive write on console lock (backgroundworkerTest.ExclusiveLock) { Console.CursorTop = 4; Console.CursorLeft = 1; var consoleOut = string.Format("Worker busy count={0}", backgroundworkerTest.Worker); Console.Write("{0}{1}", consoleOut, new string('' '', Console.WindowWidth-consoleOut.Length)); } } } } public class BackgroundworkerTest { private int worker = 0; public object ExclusiveLock = 0; public int Worker { get { return this.worker; } } public void WalkDir(string dir) { // Exclusive write on console lock (this.ExclusiveLock) { Console.CursorTop = 1; Console.CursorLeft = 1; var consoleOut = string.Format("Directory={0}", dir); Console.Write("{0}{1}", consoleOut, new string('' '', Console.WindowWidth*3 - consoleOut.Length)); } var currentDir = new System.IO.DirectoryInfo(dir); DirectoryInfo[] directoryList = null; try { directoryList = currentDir.GetDirectories(); } catch (UnauthorizedAccessException unauthorizedAccessException) { // No access to this directory, so let''s leave return; } foreach (var directoryInfo in directoryList) { var bw = new BackgroundWorker(); bw.RunWorkerCompleted += (sender, args) => { // Make sure that this worker variable is not messed up lock (this.ExclusiveLock) { worker--; } }; DirectoryInfo info = directoryInfo; bw.DoWork += (sender, args) => this.WalkDir(info.FullName); lock (this.ExclusiveLock) { // Make sure that this worker variable is not messed up worker++; } bw.RunWorkerAsync(); } } }


Utilicé Tasks con un BackgroundWorker

Puede crear cualquier cantidad de tareas y agregarlas a una lista de tareas. El trabajador se iniciará cuando se agregue una tarea, reiniciará si se agrega una tarea mientras el trabajador IsBusy, y se detendrá una vez que no haya más tareas.

Esto le permitirá actualizar la GUI de forma asíncrona todo lo que necesite sin congelarla.

Esto funciona como es para mí.

// ''tasks'' is simply List<Task> that includes events for adding objects private ObservableCollection<Task> tasks = new ObservableCollection<Task>(); // this will asynchronously iterate through the list of tasks private BackgroundWorker task_worker = new BackgroundWorker(); public Form1() { InitializeComponent(); // set up the event handlers tasks.CollectionChanged += tasks_CollectionChanged; task_worker.DoWork += task_worker_DoWork; task_worker.RunWorkerCompleted += task_worker_RunWorkerCompleted; task_worker.WorkerSupportsCancellation = true; } // ----------- worker events void task_worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { if (tasks.Count != 0) { task_worker.RunWorkerAsync(); } } void task_worker_DoWork(object sender, DoWorkEventArgs e) { try { foreach (Task t in tasks) { t.RunSynchronously(); tasks.Remove(t); } } catch { task_worker.CancelAsync(); } } // ------------- task event // runs when a task is added to the list void tasks_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { if (!task_worker.IsBusy) { task_worker.RunWorkerAsync(); } }

Ahora todo lo que necesita es crear una nueva tarea y agregarla a la Lista <>. Será ejecutado por el trabajador en el orden en que se colocó en la Lista <>

Task t = new Task(() => { // do something here }); tasks.Add(t);