views:

2149

answers:

5

Dear ladies and sirs.

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.

+4  A: 

BackgroundWorker has a completion event. Instead of waiting, call your remaining code path from the completion handler.

Jeff Wilcox
I am aware of it and the code snippet demonstrates it. However, the question is about waiting.
mark
+2  A: 

Check out the Thread.Join method!

kaze
Thats how I generally what I use, when I'm rolling out my own threads.+1 vote
Darknight
How is it relevant to the BackgroundWorker?
mark
+3  A: 

Why use a backgroundWorker at all if you want to perform the operation synchronously ?

Thomas Levesque
Who said I want to perform it synchronously? All I said that I want to wait for completion. I could perform other tasks after starting the worker, but at some point, yes, I would like to wait for completion.
mark
OK... use the RunWorkedCompleted event then, as suggested by Jeff. Just disable the UI while the BGW is working, and reenable the UI after the BGW work is complete
Thomas Levesque
+8  A: 

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.

JohannesH
You are right about my question being very similar. Somehow I have missed Fredrik's question.
mark
A: 

not quite sure what u mean by waiting. Do you mean that you want something done (by the BW) after thats done you want to do something else? Use bw.RunWorkerCompleted like you do (use a seperate function for readability) and in that callback function do you next stuff. Start a timer to check if the work doesnt take too long.

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

In the OnDoWorkLoadChildren:

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