我想使用BackgroundWorker
从GUI执行数据库事务。
如何命令BackgroundWorker
执行工作,然后在保持GUI响应的同时等待工作人员完成?
我是否必须将DoEvents
用于此目的,还是有其他方法?
答案 0 :(得分:10)
询问“如何命令BackgroundWorker
执行工作,然后等待工作人员完成,同时保持GUI响应?”真的在问“我如何使用BackgroundWorker
?”。这就是BackgroundWorker
所做的。
当你编写后台任务时,你基本上将方法分为四个部分:
所以你需要编写四种方法。第一种方法是创建BackgroundWorker
,为其DoWork
,ProgressChanged
和RunWorkerCompleted
事件添加事件处理程序,将UI置于其需要的任何状态当任务正在运行时,调用RunWorkerAsync
来启动任务。
其他三个是那三个事件处理程序。 DoWork
是完成工作的DoWorkEventHandler
,只要需要向UI报告进度,就会调用ReportProgress
。 ProgressChanged
是一个ProgressChangedEventHandler
,在ReportProgress
被调用时实际更新了用户界面。 RunWorkerCompleted
是RunWorkerCompletedEventHandler
,告诉用户界面该作业已完成。
这就是它的全部内容。
好吧,不是所有。首先,您必须确保在完成处理程序中检查并处理Error
属性。如果你不这样做,你将无法知道你的do-work方法引发异常,因为它没有在UI线程中发生,因此不会引发你可以看到的异常。 (它会显示在“输出”窗口中,如果您正在寻找它。)
其次,您必须确保DoWorkEventHandler
不会触及UI中的任何内容。如果您正在使用MVVM模式并且您没有计划这种偶然性,这可能会很棘手,因为通过数据绑定的魔力,您可能在模型中有更新UI绑定的视图的东西,这意味着在你的工作方法中操纵模型 操纵UI。假设您正在实现INotifyPropertyChanged
,这里避免麻烦的一个好方法是构建一种机制,在后台任务运行时,您的视图不会引发PropertyChanged
事件。
您还需要确定在任务运行时应禁用哪些UI部分。这取决于您的UI。答案可能是“全部”,也可能不是。使用模态表单使您的进度指示器具有吸引力的一个原因是它可以让您免于解决这个问题,因为在模态表单打开时您的整个UI都被禁用了。如果您在启动方法中禁用控件或关闭属性更改通知,则需要在完成方法中重新启用。
请记住:UI的活动部分对数据模型所做的任何更新都是跨线程数据访问错误的潜在来源。如果您的UI和后台线程更新同一个对象,则会发生错误 - 如果您未在完成处理程序中处理Error
属性,则它们会特别糟糕,并且跨线程异常会终止后台任务你不知道。如果这听起来像我在谈论这个,那是因为我在生活中犯了很多时间来做这件事。
答案 1 :(得分:2)
我通常会使用进度条以单独的模式形式等待RunWorkerCompleted。这迫使最终用户等待完成您的操作,但GUI以您可以移动表单等的方式响应。
最终用户的最佳解决方案是在状态栏中呈现一些进展,例如让他/她只执行某些不会破坏程序逻辑的操作。
这是你想要的吗?
答案 2 :(得分:1)
您使用:
Application.DoEvents();
但您应该阅读this link,以便更好地从后台工作线程更新UI
基本上,代码是这样的:
// The declaration of the textbox.
private TextBox m_TextBox;
// Updates the textbox text.
private void UpdateText(string text)
{
// Set the textbox text.
m_TextBox.Text = text;
}
public delegate void UpdateTextCallback(string text);
然后,在您的线程中,您可以在m_TextBox上调用Invoke方法,将委托传递给调用,以及参数。
m_TextBox.Invoke(new UpdateTextCallback(this.UpdateText),
new object[]{”Text generated on non-UI thread.”});
另请阅读this link,了解更多信息
答案 3 :(得分:1)
您可以像这样调用RunWorkerAsync:
this.backgroundWorker1.RunWorkerAsync();
然后你需要使用RunWorkerCompleted事件。
以下是MSDN的一个示例:
private void backgroundWorker1_RunWorkerCompleted(
object sender, RunWorkerCompletedEventArgs e)
{
// First, handle the case where an exception was thrown.
if (e.Error != null)
{
MessageBox.Show(e.Error.Message);
}
else if (e.Cancelled)
{
// Next, handle the case where the user canceled
// the operation.
// Note that due to a race condition in
// the DoWork event handler, the Cancelled
// flag may not have been set, even though
// CancelAsync was called.
resultLabel.Text = "Canceled";
}
else
{
// Finally, handle the case where the operation
// succeeded.
resultLabel.Text = e.Result.ToString();
}
// Enable the UpDown control.
this.numericUpDown1.Enabled = true;
// Enable the Start button.
startAsyncButton.Enabled = true;
// Disable the Cancel button.
cancelAsyncButton.Enabled = false;
}
就是这样。我认为没有必要调用DoEvents。在BackgroundWorker运行时,UI仍应响应。