2013-10-22 55 views
0

我使用BackgroundWorker来执行一些处理作业,同时显示前景的进度。现在我知道我不应该访问其他线程使用的属性,因为这似乎会导致各种错误。但在下面的例子中,我不认为我做的任何事情这样的,但是当它到达ShowDialog()方法BackgroundWorker神奇终止:为什么BackgroundWorker不能显示/显示对话窗口?

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方法,就好像什么也没发生一样。为什么这是错的?

+0

看看这个[问题](http://stackoverflow.com/questions/258662/unhandled-exceptions-in-backgroundworker)。正如我所假设的那样,只是因为某些东西已经完成并不意味着它已经成功完成。有一个错误属性作为包含异常的参数的一部分...从我理解的1分钟扫描中读取:) –

+0

您能提供'SomethingIsWrongDialog'构造函数的代码吗?你尝试过调试吗?任何例外? – etaiso

+0

'm_backgroundWorker_DoWork'在另一个线程上运行。正如你自己提到的,你不应该那样做。 – sloth

回答

3

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

他们当然做。像剪贴板,拖放和shell对话框(OpenFileDialog等)的基本功能需要一个STA线程。

配置为单线程单元的线程可以为基本上线程不安全的组件提供线程安全保证。其中许多是特别是在用户界面中操作的那种。 STA线程所做的一件事是工作者线程不会做的是抽取消息循环。 WPF中的调度器循环。这提供了一种进行线程安全调用的方法,您可以在该STA线程上调用方法。由Dispatcher.Begin/Invoke()方法暴露在WPF中。非线程安全的组件知道如何自动创建该调用,就像在自己的WPF代码中使用Dispatcher.BeginInvoke()自己写的一样。如果你编写一个COM组件,那么你甚至不需要编写BeginInvoke(),COM就会自动执行它。

加入MTA的线程(如BackgroundWorker使用的线程池线程)无法提供此保证。它没有那个关键的调度程序循环。所以你在这样的线程上显示的用户界面只会出现故障,像复制/粘贴这样的简单工作将不再起作用。就像消息所说,许多UI组件都需要这个。

调度程序循环的核心属性是其对producer-consumer problem的解决方案。操作系统是生产者,例如当用户操作鼠标和键盘时生成异步通知。而你的UI是消费者。

1

后台工作程序不在UI线程中执行。你想要做什么是运行背景,然后切换回UI并显示一些内容。

这是错的。对我来说最简单的就是将一些IProgressNotifier对象传递给后台工作者。 public interface 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"); 
     } 
    } 
} 
+0

至于我,我回答正确(:BackgroundWorker是工作者,而不是UI工作者。它应该只是做一些长期的行动,并通知父进程\取消。 –

+0

对不起,我删除了我的评论后,从OP我明显误解了他们之后:) –

+0

其实我不喜欢我的也不是TDull的解决方案,它太棘手,不应该使用Backgroundworker这种方式 –

0

你的BackgroundWorker的后台线程运行,UI任务必须在主(UI)线程中完成的。这会给你一些关于如何在非UI线程中显示表单的知识:How to Create Form from within non gui thread 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(); 
    } 

} 
+0

而不是抛出异常它会更容易设置((BackgroundWorker )sender).Result = new Exception(“It's failed because ...”);并返回 –

+0

是的,你可以避免RunWorkerCompleted事件中的if测试,但是如果你将Result属性用于其他目的,必须检查结果对象的类型。 – TDull

相关问题