Search code examples
c#multithreadingbackgroundworker

Nested BackgroundWorkers: RunWorkerCompleted calls on wrong thread?


I'm working on asynchronous operation which needs to invoke further asynchronous tasks. I'm trying to keep it simple by using BackgroundWorkers, with the result being that one BackgroundWorker's DoWork() callback calls a method which creates a second BackgroundWorker, like so (minus error checking and all that jazz for brevity):

class Class1
{
    private BackgroundWorker _worker = null;

    public void DoSomethingAsync()
    {
        _worker = new BackgroundWorker();
        _worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_worker_RunWorkerCompleted);
        _worker.DoWork += new DoWorkEventHandler(_worker_DoWork);
        _worker.RunWorkerAsync();
    }

    void _worker_DoWork(object sender, DoWorkEventArgs e)
    {
        Class2 foo = new Class2();
        foo.DoSomethingElseAsync();
        while(foo.IsBusy) Thread.Sleep(0);  // try to wait for foo to finish.
    }

    void _worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        // do stuff
    }
}

class Class2
{
    private BackgroundWorker _worker = null;
    Thread _originalThread = null;

    public AsyncCompletedEventHandler DoSomethingCompleted;

    public bool IsBusy { get { return _worker != null && _worker.IsBusy; } }

    public void DoSomethingElseAsync()
    {
        _originalThread = Thread.CurrentThread;

        _worker = new BackgroundWorker();
        _worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_worker_RunWorkerCompleted);
        _worker.DoWork += new DoWorkEventHandler(_worker_DoWork);
        _worker.RunWorkerAsync();
    }

    void _worker_DoWork(object sender, DoWorkEventArgs e)
    {
        // do stuff
    }

    void _worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        Debug.Assert(Thread.CurrentThread == _originalThread);  // fails

        // Assuming the above Assert() were excluded, the following event would be raised on the wrong thread.
        if (DoSomethingCompleted != null) DoSomethingCompleted(this, new AsyncCompletedEventArgs(e.Error, e.Cancelled, null));
    }
}

So the problem is, I'm expecting Class2._Worker_RunWorkerCompleted() to execute on the same thread on which Class2.DoSomethingElseAsync() was called. This never happens - instead, the callback runs on a completely new thread.

Here's my suspicion: Class1's _worker_DoWork() never returns, which means that thread would never get back to an event listener, even if one existed (I suspect one doesn't). On the other hand, if _worker_DoWork() did return, Class1's BackgroundWorker would automatically finish prematurely - it needs to wait for Class2 to finish working before it can finish its work.

That leads to two questions:

  1. Is my suspicion correct?
  2. What's the best way to nest asynchronous operations like this? Can I salvage the BackgroundWorker approach, or is there some other, more suitable technique?

Solution

  • If a BackgroundWorker is created on the UI thread, DoWork will run on a thread pool thread and RunWorkerCompleted will run on the UI thread.

    If a BackgroundWorker is created on a background thread (ie not the UI thread) DoWork will still run on a thread pool thread and RunWorkerCompleted will also run on a thread pool thread.

    In your case, since you can't marshal a call to an arbitrary (thread pool) thread, you won't be able to guarantee the behaviour you want, although you might want to take a look at System.Threading.SynchronizationContext.