40

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.

abatishchev
  • 98,240
  • 88
  • 296
  • 433
mark
  • 59,016
  • 79
  • 296
  • 580
  • I believe a background worker can have multiple running threads, wich one do you want to wait for? And what happens if your waiting code gets called when another background worker just got scheduled? I believe this is the reason this question keeps reapearing. – CodingBarfield Aug 26 '09 at 11:48
  • 1
    @Barfieldmv: Thats why the AutoResetEvent is such an elegant solution. You can wait for one task or multiple, regardless what thread they're running on. For multiple tasks just use the static method `AutoResetEvent.WaitAll(new[] {doneEvent1, doneEvent2})` – JohannesH Aug 26 '09 at 14:18
  • @JohannesH to be able to wait on multiple events you have to make sure that they fire and get handled. I'm afraid that blocking on a UI thread for example will block all events on a windows form so that you can wait forever. If i remember correctly a backgroundworker processes his Dowork event on a background thread and fires the WorkCompleted event on the main thread if the main thread isnt blocked. This enters the realm of hard to debug multiple threading at least for me. – CodingBarfield Aug 26 '09 at 14:24
  • @Barfieldmv: You're right, this is not as straightforward as I thought it was. However, in my example I call doneEvent.Set() from the DoWork handler... So at least that is legitm thread wise. However, the WaitAll call wont work since WinForms and WPF UI thread runs in STA mode, so I retract my previous statement in that regard. Instead, you could call WaitAny for N times, where N is the number of wait handles. But my solution still has the problem that it won't, and can't, wait for the RunWorkerCompleted event handlers to finish since they're also blocked by the call to WaitAny or WaitOne. – JohannesH Aug 26 '09 at 15:37
  • 1
    I must point out that the problem with the RunWorkerCompleted handler being blocked is also a problem in Mark's original `while(!finished...){...}` solution. So that solution will always throw the TimedoutException. As far as I can tell it will only be possible to wait for the DoWork handler to finish, not the RunWorkerCompleted handler due to the nature of the BackgroundWorker. Also, it will be impossible to guarantee that a job have started, but you could solve this problem with a timeout value. – JohannesH Aug 26 '09 at 16:05
  • @JohannesH Actually my piece of code works fine when the thread is not a UI thread, which is true for the unit test thread. But I do have unit tests that simulate WPF thread by creating a Dispatcher and running the unit test code within the Dispatcher context, fooling the code into thinking it is a UI thread. In such scenarios, I did not try this kind of code, but rather use another approach. I am interested if there is a unified solution. – mark Aug 26 '09 at 18:46
  • @mark use AutoResetEvent in another backgroundworker. See [my answer](http://stackoverflow.com/a/29137389/836376) below – KevinBui Mar 19 '15 at 04:51

10 Answers10

46

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.

Community
  • 1
  • 1
JohannesH
  • 6,430
  • 5
  • 37
  • 71
  • You are right about my question being very similar. Somehow I have missed Fredrik's question. – mark Aug 26 '09 at 18:10
20

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.
    }
    
Azhar Khorasany
  • 2,712
  • 16
  • 20
  • 2
    Another thing I forgot to mention here is because you are adding the events as += ... you must do -= when a background worker completes its execution, otherwise the background worker won't dispose. – Azhar Khorasany Nov 29 '11 at 13:50
  • @Kiquenet : Well isn't this the core functionality you are looking for? You call the method CheckAllThreadsHaveFinishedWorking() where you are waiting for all the background workers to be completed before the program continues. – Azhar Khorasany Jul 16 '12 at 14:49
7

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

Jeff Wilcox
  • 6,375
  • 1
  • 24
  • 31
  • 11
    I am aware of it and the code snippet demonstrates it. However, the question is about waiting. – mark Aug 26 '09 at 10:49
2

VB.NET

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

You can use this to chain multiple events. (pseudo code to follow)

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")
Thomas Weller
  • 55,411
  • 20
  • 125
  • 222
Don
  • 29
  • 1
2

Checking backgrWorker.IsBusy in the loop with Application.DoEvents() is not a nicely way.

I agree with @JohannesH, you should definitively use AutoResetEvent as a elegant solution. But not using it in UI Thread, it will cause main thread blocked; it should come from another background worker thread.

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");
}
KevinBui
  • 880
  • 15
  • 27
2

This question is old but I don't think the author got the answer he was looking for.

This is a bit dirty, and it's in vb.NET but works for me

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
Justin
  • 84,773
  • 49
  • 224
  • 367
goughy000
  • 668
  • 4
  • 13
0

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
}
RvdK
  • 19,580
  • 4
  • 64
  • 107
0

I was also looking for a suitable solution. I solved the waiting with an exclusive lock. The critical path in code are writing to a public container (here the console) and increasing or decreasing the workers. No thread should interfere while writing to this variable, otherwise the count is not guaranteed anymore.

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();
        }
    }
}
be981
  • 11
  • 1
0

I used Tasks with a BackgroundWorker

You can create any number of tasks and add them to a list of tasks. The worker will start when a task is added, restart if a task is added while the worker IsBusy, and stop once there are no more tasks.

This will allow you to update the GUI asynchronously as much as you need to without freezing it.

This works as is for me.

    // '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();
        }
    }

Now all you need is to create a new Task and add it to the List<>. It will be run by the worker in the order it was placed into the List<>

Task t = new Task(() => {

        // do something here
    });

    tasks.Add(t);
Beefjeff
  • 371
  • 4
  • 12
-1

In OpenCV exists function WaitKey. Ir allows solve this issue in that way:

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