Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to wait correctly until BackgroundWorker completes?

Observe the following piece of code:

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

Now suppose I want to wait until bw finishes working. What is the right way to do so?

My solution is this:

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"); } 

But I do not like it.

I have considered replacing the finished flag with a synchronization event, set it in the RunWorkerCompleted handler and block on it later instead of doing the while-sleep loop.

Alas, it is wrong, because the code may run in the WPF or WindowsForm synchronization context, in which case I would block the same thread as the RunWorkerCompleted handler runs on, which is clearly not very smart move.

I would like to know of a better solution.

Thanks.

EDIT:

P.S.

  • The sample code is so contrived intentionally to clarify my question. I am perfectly aware of the completion callback and yet I want to know how to wait till completion. That is my question.
  • I am aware of Thread.Join, Delegate.BeginInvoke, ThreadPool.QueueUserWorkItem, etc... The question is specifically about BackgroundWorker.

EDIT 2:

OK, I guess it will be much easier if I explain the scenario.

I have a unit test method, which invokes some asynchronous code, which in turn ultimately engages a BackgroundWorker to which I am able to pass a completion handler. All the code is mine, so I can change the implementation if I wish to. I am not going, however, to replace the BackgroundWorker, because it automatically uses the right synchronization context, so that when the code is invoked on a UI thread the completion callback is invoked on the same UI thread, which is very good.

Anyway, it is possible that the unit test method hits the end before the BW finishes its work, which is not good. So I wish to wait until the BW completes and would like to know the best way for it.

There are more pieces to it, but the overall picture is more or less like I have just described.

like image 239
mark Avatar asked Aug 26 '09 07:08

mark


People also ask

How does DoWork pass parameters to BackgroundWorker?

You can send a parameter to the background operation using the RunWorkerAsync() method. You can receive this parameter by using the Argument property of the instance of DoWorkEventArgs in the DoWork event handler then you cast it to use it in the background operation.

How do I use BackgroundWorker?

The steps are extremely simple: Create a BackgroundWorker object. Tell the BackgroundWorker object what task to run on the background thread (the DoWork function). Tell it what function to run on the UI thread when the work is complete (the RunWorkerCompleted function).

What is the difference between BackgroundWorker and thread?

A BackgroundWorker is a ready to use class in WinForms allowing you to execute tasks on background threads which avoids freezing the UI and in addition to this allows you to easily marshal the execution of the success callback on the main thread which gives you the possibility to update the user interface with the ...

How do I start BackgroundWorker?

You can create the BackgroundWorker programmatically or you can drag it onto your form from the Components tab of the Toolbox. If you create the BackgroundWorker in the Windows Forms Designer, it will appear in the Component Tray, and its properties will be displayed in the Properties window.


2 Answers

Try using the AutoResetEvent class like this:

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(); 

Caution: You should make sure that doneEvent.Set() is called no matter what happens. Also you might want to provide the doneEvent.WaitOne() with an argument specifying a timeout period.

Note: This code is pretty much a copy of Fredrik Kalseth answer to a similar question.

like image 140
JohannesH Avatar answered Sep 28 '22 11:09

JohannesH


To wait for a background worker thread (single or multiple) do the following:

  1. Create a List of Background workers you have programatically created:

    private IList<BackgroundWorker> m_WorkersWithData = new List<BackgroundWorker>(); 
  2. Add the background worker in the list:

    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 the following function to wait for all workers in the List:

    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. } 
like image 38
Azhar Khorasany Avatar answered Sep 28 '22 11:09

Azhar Khorasany