0

我正在使用 aBackgroundWorker来执行一些处理工作,同时在前台显示进度 basr。现在我知道我不应该访问其他线程使用的属性,因为这似乎会导致各种错误。但是在下面的例子中,我不认为我在做这样的事情,但是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();
    }
}

所以我的程序将命中 的构造函数SomethingIsWrongDialog,然后,而不是崩溃,只是停止线程,并执行该m_backgroundWorker_RunWorkerCompleted方法,就好像什么都没发生一样。为什么这是错误的?

4

3 回答 3

3

调用线程必须是 STA,因为很多 UI 组件都需要这个

他们当然会。剪贴板、拖放和外壳对话框(OpenFileDialog 等)等基本功能需要 STA 线程。

配置为单线程单元的线程可以为基本上线程不安全的组件提供线程安全保证。其中许多是,特别是在用户界面中操作的那种。STA 线程做而工作线程不做的一件事是泵送消息循环。WPF 中的调度程序循环。它提供了一种进行线程安全调用的方法,您可以调用该 STA 线程上的方法。通过 Dispatcher.Begin/Invoke() 方法在 WPF 中公开。非线程安全的组件知道如何自动进行调用,就像您在自己的 WPF 代码中使用 Dispatcher.BeginInvoke() 自己编写它一样。如果您编写了一个 COM 组件,那么您甚至不必编写 BeginInvoke(),COM 会自动完成。

加入 MTA 的线程,如 BackgroundWorker 使用的线程池线程,不能提供这种保证。它没有那个关键的调度程序循环。因此,您在此类线程上显示的 UI 只会出现故障,复制/粘贴之类的简单操作将不再起作用。就像消息说的那样,许多 UI 组件都需要这个。

调度程序循环的核心属性是它对生产者-消费者问题的解决方案。以操作系统为生产者,例如当用户操作鼠标和键盘时生成异步通知。你的用户界面就是消费者。

于 2013-10-22T12:22:05.480 回答
1

后台工作人员不在 UI 线程中执行。你想要的是运行后台,然后切换回 UI 并显示一些东西。

这是错的。对我来说最简单的是将一些 IProgressNotifier 对象传递给后台工作人员。公共接口 IProgressNotifier { bool ShowWarning(string text);}

那么你的 ViewModel(你在其中调用这个后台工作执行)应该实现它并通过它的调度程序显示这个窗口。

编辑示例更改:

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");
        }
    }
}
于 2013-10-22T11:32:28.207 回答
0

您的 backgroundworker 在后台线程上运行,UI 任务必须在主 (UI) 线程中完成。这将使您了解如何在非 UI 线程中显示表单:如何从非 gui 线程 C# 中创建表单

但是,我建议使用不同的方法,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();
     } 

}
于 2013-10-22T11:50:17.143 回答