[英]Why can a BackgroundWorker not Show/ShowDialog a window?
我正在使用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
方法,而不是崩溃,而不是崩溃。 为什么会这样呢?
调用线程必须是STA,因为许多UI组件都需要STA
他们当然会。 剪贴板,拖放和外壳对话框(OpenFileDialog等)等基本功能都需要STA线程。
配置为单线程单元的线程可以为基本上是线程不安全的组件提供线程安全保证。 它们中的许多是,特别是在用户界面中运行的类型。 STA线程执行而工作线程不执行的一件事是泵送消息循环。 WPF中的分派器循环。 它提供了进行线程安全的调用的方法,您可以在该STA线程上调用方法。 在WPF中由Dispatcher.Begin / Invoke()方法公开。 非线程安全的组件知道如何自动进行该调用,就像您自己在WPF代码中使用Dispatcher.BeginInvoke()编写该调用一样。 如果您编写一个COM组件,那么您甚至不必编写BeginInvoke(),COM就会自动完成。
加入MTA的线程(例如BackgroundWorker使用的线程池线程)无法提供此保证。 它没有关键的调度程序循环。 因此,您在此类线程上显示的UI只会出现故障,复制/粘贴之类的简单操作将不再起作用。 就像消息说的那样,许多UI组件都需要这样做。
调度程序循环的核心属性是它对生产者-消费者问题的解决方案。 以操作系统为生产者,例如,当用户操作鼠标和键盘时,将生成异步通知。 您的用户界面就是消费者。
后台工作程序不在UI线程中执行。 您正在尝试的是运行后台,然后切换回UI并显示一些内容。
这是不对的。 对我来说,最简单的方法是将一些IProgressNotifier对象传递给backgrround worker。 公共接口IProgressNotifier {bool ShowWarning(string text);}
那么您的ViewModel(在这里您称为backgrround工人执行程序)应该实现它,并通过其调度程序显示此窗口。
编辑样本更改:
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");
}
}
}
您的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();
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.