[UPDATE 7/19/2011] Stephen pointed me to his article covering this and more in February issue of MSDN Magazine, and I recommend it: http://msdn.microsoft.com/en-us/magazine/gg598924.aspx.
We hit this recently, so I thought I’d post this email from Chad, a developer on version control, for anyone else who may have missed this subtlety.
Today we discovered some of our code was making an incorrect assumption about the behavior of BackgroundWorker, so I thought it might be useful to send a note detailing what we found.
Our code assumed BackgroundWorker would always call ProgressChanged and RunWorkerCompleted on the UI thread. This mistake was based on the assumption that BackgroundWorker saved off the SynchronizationContext for the thread on which it was created.
After reviewing the BackgroundWorker code, we found that it actually saves the SynchronizationContext for the thread where RunWorkerAsync is called (by calling AsyncOperationManager.CreateOperation). Then, ProgressChanged and RunWorkerCompleted are called on that thread if it is still running. If not, the events appear to be called on a random thread.
This of course leads to a crash when there are attempts to update UI from the wrong thread. If you are relying on BackgroundWorker to return you to the UI thread, make sure to only call RunWorkerAsync from the UI thread.
Hope this helps!