简体   繁体   English

为什么BackgroundWorker无法显示/显示对话框窗口?

[英]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. 我正在使用BackgroundWorker执行一些处理作业,同时在前台显示进度条。 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: 但是在下面的示例中,我认为我没有做任何事情,但是BackgroundWorker在到达ShowDialog()方法时神奇地终止了:

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. 因此,我的程序将命中SomethingIsWrongDialog的构造函数,然后停止崩溃,并执行m_backgroundWorker_RunWorkerCompleted方法,而不是崩溃,而不是崩溃。 Why is this wrong? 为什么会这样呢?

The calling thread must be STA, because many UI components require this 调用线程必须是STA,因为许多UI组件都需要STA

They certainly do. 他们当然会。 Basic features like the clipboard, drag+drop and the shell dialogs (OpenFileDialog etc) require an STA thread. 剪贴板,拖放和外壳对话框(OpenFileDialog等)等基本功能都需要STA线程。

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. STA线程执行而工作线程不执行的一件事是泵送消息循环。 The dispatcher loop in WPF. WPF中的分派器循环。 Which provides a way to make thread-safe calls, you can call methods on that STA thread. 它提供了进行线程安全的调用的方法,您可以在该STA线程上调用方法。 Exposed in WPF by the Dispatcher.Begin/Invoke() methods. 在WPF中由Dispatcher.Begin / Invoke()方法公开。 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. 非线程安全的组件知道如何自动进行该调用,就像您自己在WPF代码中使用Dispatcher.BeginInvoke()编写该调用一样。 If you author a COM component then you don't even have to write BeginInvoke(), COM does it automatically. 如果您编写一个COM组件,那么您甚至不必编写BeginInvoke(),COM就会自动完成。

A thread that joined the MTA, like the threadpool thread used by BackgroundWorker, cannot provide this guarantee. 加入MTA的线程(例如BackgroundWorker使用的线程池线程)无法提供此保证。 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. 因此,您在此类线程上显示的UI只会出现故障,复制/粘贴之类的简单操作将不再起作用。 Like the message says, many UI components require this. 就像消息说的那样,许多UI组件都需要这样做。

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. 后台工作程序不在UI线程中执行。 What are you trying is to run background, then switch back to UI and show something. 您正在尝试的是运行后台,然后切换回UI并显示一些内容。

It's wrong. 这是不对的。 The easiest for me is to pass some IProgressNotifier object to backgrround worker. 对我来说,最简单的方法是将一些IProgressNotifier对象传递给backgrround worker。 public interface IProgressNotifier { bool ShowWarning(string text);} 公共接口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. 那么您的ViewModel(在这里您称为backgrround工人执行程序)应该实现它,并通过其调度程序显示此窗口。

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. 您的backgroundworker在后台线程上运行,UI任务必须在主(UI)线程中完成。 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# 这将使您对如何在非UI线程中显示表单有一些了解: 如何从非GUI线程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. 但是,我建议使用另一种方法,RunWorkerCompleted事件在UI线程上运行,因此应该在此处显示表单。

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();
     } 

}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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