Search code examples
c#wpfbackgroundworker

Why can a BackgroundWorker not Show/ShowDialog a window?


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?


Solution

  • 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.