How could this work?

I’m sure most of you know about BackgroundWorker, a new component in
.NET 2.0, that allows UI code to run tasks asynchronously, without
running into cross thread update issues. But what would happen if the
thread that launches the background worker is not a UI thread i.e. it
doesn’t run a message pump?

The answer is quite surprising and caused a piece of working code like this


AutoResetEvent waitForWorker = new AutoResetEvent(false);
void Method()
{
BackgroundWorker worker = new BackgroundWorker();
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_Completed)
worker.RunAsync();
waitForWorker.WaitOne();
}

void worker_Completed(...)
{
waitForWorker.Set();
}


to stop working after making a tiny modification like this

class MyForm : Form {}

AutoResetEvent waitForWorker = new AutoResetEvent(false);
void Method()
{
MyForm f = new MyForm();
BackgroundWorker worker = new BackgroundWorker();
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_Completed)
worker.RunAsync();
waitForWorker.WaitOne();
}

void worker_Completed(...)
{
waitForWorker.Set();
}


This caused Method() to hang indefinitely, because worker_Completed
never got to run. That makes sense, because the thread running method
is currently blocked on a call to waitForWorker.WaitOne() and
background worker would require that thread to be pumping messages to
get worker_Completed to run on that thread. But how did it work before?
The first code snippet also blocks on WaitOne(), yet worker_Completed
does run and everything works.



This is where things get interesting. A peek into BackgroundWorker via
Reflector revealed that it internally uses the new
AsyncOperationManager class to deal with running its event handlers
like ProgressChanged and RunWorkerCompleted on the thread that called
RunAsync. AsyncOperationManager is a general class that uses a
SynchronizationContext object to do its stuff and it’s
SynchronizationContext that does the heavy lifting of running code on
different threads. To get an instance of SynchronizationContext,
AsyncOperationManager tries to access the
SynchronizationContext.Current property, which retrieves the
corresponding instance for the current thread.



The default behavior of the SynchronizationContext class is to simply
delegate requests to run code on other threads to the threadpool. There
are subclasses of SynchronizationContext, like
System.Windows.Forms.WindowsFormsSynchronizationContext, that customize
the behavior to do something different.
WindowsFormsSynchronizationContext, for example, uses
Control.BeginInvoke internally to marshal code to run on the thread
that called RunAsync.



Can you see the problem now? Instantiating an instance of a Form
derived class caused the SynchronizationContext of the current thread
to change to WindowsFormsSynchronizationContext, which would use
Control.BeginInvoke. And that would not work unless the target thread
is pumping messages. The first sample worked because there was no
specific SynchronizationContext available, so an instance of the base
SynchronizationContext class itself is created. That would simply call
ThreadPool.QueueUserWorkItem to run the event handlers, which obviously
doesn’t require the target thread to be pumping.



The lesson – be wary of using BackgroundWorker from non-UI threads, the
behavior might change depending on objects previously instantiated on
the thread.

 

2 thoughts on “How could this work?”

  1. So, how can you make it work?

    I have Main Form that uses background worker. And it does stuck on bgWorker_DoWork(…) unless I call waitHandle.Set() at the end of bgWorker_DoWork().

    But I don’t want to do that, I want to call waitHandle.Set() on bgWorker_RunWorkerCompleted()

    Is it possible?

    Thanks,
    Sam

Leave a Reply

Your email address will not be published. Required fields are marked *


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>