我使用BackgroundWorker
执行一些处理作业,同时在前台显示进度。现在我知道我不应该访问其他线程使用的属性,因为这似乎会导致各种错误。但是在下面的例子中,我认为我没有做任何这样的事情,但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
方法,就好像什么都没发生一样。为什么这是错的?
答案 0 :(得分:3)
调用线程必须是STA,因为许多UI组件需要此
他们当然这样做。剪贴板,拖放和shell对话框(OpenFileDialog等)等基本功能需要一个STA线程。
配置为单线程单元的线程可以为基本上线程不安全的组件提供线程安全保证。其中许多是,特别是在用户界面中运行的那种。 STA线程做的一件事就是工作线程没有做的事情是泵出一个消息循环。 WPF中的调度程序循环。这提供了一种进行线程安全调用的方法,您可以在该STA线程上调用方法。 Dispatcher.Begin / Invoke()方法在WPF中公开。非线程安全的组件知道如何自动进行该调用,就像您自己在自己的WPF代码中使用Dispatcher.BeginInvoke()编写它一样。如果您创作了一个COM组件,那么您甚至不必编写BeginInvoke(),COM会自动执行。
加入MTA的线程(如BackgroundWorker使用的线程池线程)无法提供此保证。它没有那个关键的调度程序循环。因此,您在这样的线程上显示的UI只会出现故障,复制/粘贴之类的简单操作将不再起作用。就像消息所说的那样,许多UI组件都需要这个。
调度程序循环的核心属性是producer-consumer problem的解决方案。操作系统是生产者,例如当用户操作鼠标和键盘时生成异步通知。而你的用户界面就是消费者。
答案 1 :(得分:1)
后台工作程序不在UI线程中执行。你在尝试的是运行背景,然后切换回UI并显示一些内容。
这是错的。对我来说最简单的方法是将一些IProgressNotifier对象传递给backgrround worker。 公共接口IProgressNotifier {bool ShowWarning(string text);}
然后你的ViewModel(你调用这个backgrround worker执行)应该实现它并通过它的调度程序显示这个窗口。
EDIT 样本变更:
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");
}
}
}
答案 2 :(得分:0)
您的后台工作程序在后台线程上运行,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();
}
}