简体   繁体   中英

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?

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.

Background Worker is executing not in UI thread. What are you trying is to run background, then switch back to UI and show something.

It's wrong. The easiest for me is to pass some IProgressNotifier object to backgrround worker. public interface IProgressNotifier { bool ShowWarning(string text);}

then your ViewModel (where you called this backgrround worker execution) should implement it and show this window via its dispatcher.

EDIT Sample changes:

interface IWorkNotifier
{
    void ShowError(string text);
}
public class ProcessingWindow: IWorkNotifier
   ...

public void ShowError(string text)
{
    Application.Current.Dispatcher.Invoke((Action)(() => DoShowError(text)));
}
private void DoShowError(string text)
{
    SomethingIsWrongDialog dialog = new SomethingIsWrongDialog();
    dialog.ShowDialog();
}
void ProcessingProgressWindow_Loaded(object sender, RoutedEventArgs e)
    {
        m_backGroundWorker.RunWorkerAsync(this);
    }
private void m_backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
    var arg = (IWorkNotifier)e.Argument;
    foreach (ProcessingJob job in m_processingJobs)
    {
        if (job.somethingIsWrong)
        {
            arg.ShowError("shit happened");
        }
    }
}

Your backgroundworker runs on a background thread, UI tasks must be done in the main (UI) thread. This will give you some insight on how to display a form in a non-UI thread: How to Create Form from within non gui thread C#

However, I would recomend using a different approach, the RunWorkerCompleted event runs on the UI thread, so this is where you should show your form.

private void m_backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
    foreach (ProcessingJob job in m_processingJobs )
    {
         if (job.somethingIsWrong)
         {
            throw new Exception("It failed because...");             
         }
    }
 }

private void m_backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
     if (e.Exception != null)
     {            
        SomethingIsWrongDialog dialog = new SomethingIsWrongDialog(); 
        dialog.showDialog();
     }
     else
     {
        Debug.WriteLine("Finished!");
        this.Close();
     } 

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM