I am using a BackgroundWorker
to perform some processing jobs, while showing a progress basr on the foreground. Now I know that I am not supposed to access properties that are used by other threads, as this seems to cause all sorts of errors. But in the following example, I don't think I am doing anything such, yet the BackgroundWorker
magically terminates when it reaches the ShowDialog()
method:
public class ProcessingWindow
{
List<ProcessingJob> m_processingJobs = new List<ProcessingJob>();
private BackgroundWorker m_backGroundWorker = new BackgroundWorker();
public ProcessingWindow()
{
InitializeComponent();
/* Fill m_processingJobs with jobs */
m_backGroundWorker.WorkerReportsProgress = true;
m_backGroundWorker.WorkerSupportsCancellation = true;
m_backGroundWorker.DoWork += m_backgroundWorker_DoWork;
m_backGroundWorker.ProgressChanged += m_backgroundWorker_ProgressChanged;
m_backGroundWorker.RunWorkerCompleted += m_backgroundWorker_RunWorkerCompleted;
this.Loaded += ProcessingProgressWindow_Loaded;
}
void ProcessingWindow_Loaded(object sender, RoutedEventArgs e)
{
m_backGroundWorker.RunWorkerAsync();
}
private void m_backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
foreach (ProcessingJob job in m_processingJobs )
{
if (job.somethingIsWrong)
{
SomethingIsWrongDialog dialog = new SomethingIsWrongDialog(); // <-- Here it crashes!
// This statement is therefore never reached:
dialog.showDialog();
}
}
}
private void m_backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
Debug.WriteLine("Finished!");
this.Close();
}
}
So my program will hit the constructor of SomethingIsWrongDialog
, and then, instead of crashing, just stop the thread, and perform the m_backgroundWorker_RunWorkerCompleted
method as if nothing happened. Why is this wrong?
The calling thread must be STA, because many UI components require this
They certainly do. Basic features like the clipboard, drag+drop and the shell dialogs (OpenFileDialog etc) require an STA thread.
A thread that's configured as a Single Threaded Apartment can provide thread-safety guarantees to components that are fundamentally thread-unsafe. Many of them are, particularly the kind that operates in a user interface. One thing that an STA thread does that a worker thread doesn't do is pump a message loop. The dispatcher loop in WPF. Which provides a way to make thread-safe calls, you can call methods on that STA thread. Exposed in WPF by the Dispatcher.Begin/Invoke() methods. Components that are not thread-safe know how to make that call automatically, much like you'd write it yourself by using Dispatcher.BeginInvoke() in your own WPF code. If you author a COM component then you don't even have to write BeginInvoke(), COM does it automatically.
A thread that joined the MTA, like the threadpool thread used by BackgroundWorker, cannot provide this guarantee. It doesn't have that crucial dispatcher loop. So the UI you'd display on such a thread will just malfunction, simple things like Copy/Paste will not work anymore. Like the message says, many UI components require this.
The core attribute of a dispatcher loop is its solution to the producer-consumer problem. With the operating system being the producer, generating asynchronous notifications when the user operates the mouse and keyboard for example. And your UI being the consumer.