我有一个用于数据库分页的简单用户控件,它使用一个 Controller 来执行实际的 DAL 调用。我用 BackgroundWorker
执行繁重的工作,并在 OnWorkCompleted
上事件我重新启用一些按钮,更改 TextBox.Text
属性并为父窗体引发事件。
Form A 包含我的 UserControl。当我单击某个打开表单 B 的按钮时,即使我没有在“那里”做任何事情而只是关闭它,并尝试从我的数据库中引入下一页,OnWorkCompleted
在工作线程(而不是我的主线程)上被调用,并抛出跨线程异常。
此刻我添加了一个 InvokeRequired
的支票在处理程序那里,但这不是 OnWorkCompleted
的重点是在主线程上调用?为什么它不能按预期工作?
编辑:
我已经设法将问题缩小到 arcgis 和 BackgroundWorker
.我有以下解决方案,它向 arcmap 添加了一个命令,它打开了一个简单的 Form1
有两个按钮。
第一个按钮运行 BackgroundWorker
休眠 500 毫秒并更新计数器。
在RunWorkerCompleted
它检查 InvokeRequired
的方法,并更新标题以显示该方法最初是在主线程还是工作线程中运行。
第二个按钮刚打开Form2
, 什么都不包含。
一开始,所有对RunWorkerCompletedare
的调用是在主线程内创建的(正如预期的那样 - 这就是 RunWorkerComplete 方法的重点,至少根据我从 MSDN 对 BackgroundWorker
的了解)
开闭后Form2
, RunWorkerCompleted
总是在工作线程上被调用。我想补充一点,我可以按原样保留这个问题的解决方案(检查 InvokeRequired
方法中的 RunWorkerCompleted
),但我想了解为什么它的发生违背了我的预期。在我的“真实”代码中,我希望始终知道 RunWorkerCompleted
正在主线程上调用方法。
我设法将问题定位在 form.Show();
命令在我的 BackgroundTesterBtn
- 如果我使用 ShowDialog()
相反,我没有遇到任何问题(RunWorkerCompleted
总是在主线程上运行)。我确实需要使用 Show()
在我的 ArcMap 项目中,这样用户就不会绑定(bind)到表单。
我还尝试在普通 WinForms 项目上重现该错误。我添加了一个简单的项目,它只在没有 ArcMap 的情况下打开第一个表单,但在那种情况下我无法重现错误 - RunWorkerCompleted
在主线程上运行,我是否使用了Show()
或 ShowDialog()
, 打开前后 Form2
.我尝试在 Form1
之前添加第三种形式作为主要形式, 但它并没有改变结果。
Here是我的简单 sln (VS2005sp1) - 它需要
ESRI.ArcGIS.ADF(9.2.4.1420)
ESRI.ArcGIS.ArcMapUI(9.2.3.1380)
ESRI.ArcGIS.SystemUI (9.2.3.1380)
最佳答案
Isn't the whole point of
OnWorkCompleted
is to be called on the Main thread? Why wouldn't it work as expected?
不,不是。
你不能只是在任何旧线程上运行任何旧东西。线程不是礼貌的对象,您可以简单地说“请运行它”。
更好的线程心智模型是 cargo 列车。一旦开始,它就会走上自己的轨道。你不能改变它的路线或停止它。如果你想影响它,你要么等到它到达下一个火车站(例如:让它手动检查一些事件),要么让它出轨(Thread.Abort
和 CrossThread 异常有与火车出轨的后果大致相同……当心!)。
Winforms 控件有点支持这种行为(它们有 Control.BeginInvoke
可以让你在 UI 线程上运行任何函数),但这只是因为它们有一个特殊 Hook 到 Windows UI 消息泵并编写一些特殊的处理程序。继续上面的类比,他们的火车在车站登记并定期寻找新的方向,您可以使用该设施发布您自己的方向。
BackgroundWorker
是为通用目的而设计的(它不能绑定(bind)到 windows GUI),因此它不能使用 windows Control.BeginInvoke
功能。它必须假设您的主线程是一个不可阻挡的“火车”,做它自己的事情,所以完成的事件必须在工作线程中运行或根本不运行。
但是,当您使用 winforms 时,在您的 OnWorkCompleted
处理程序中,您可以使用 BeginInvoke
让窗口执行另一个回调我上面提到的功能。像这样:
// Assume we're running in a windows forms button click so we have access to the
// form object in the "this" variable.
void OnButton_Click(object sender, EventArgs e )
var b = new BackgroundWorker();
b.DoWork += ... blah blah
// attach an anonymous function to the completed event.
// when this function fires in the worker thread, it will ask the form (this)
// to execute the WorkCompleteCallback on the UI thread.
// when the form has some spare time, it will run your function, and
// you can do all the stuff that you want
b.RunWorkerCompleted += (s, e) { this.BeginInvoke(WorkCompleteCallback); }
b.RunWorkerAsync(); // GO!
}
void WorkCompleteCallback()
{
Button.Enabled = false;
//other stuff that only works in the UI thread
}
Your RunWorkerCompleted event handler should always check the Error and Cancelled properties before accessing the Result property. If an exception was raised or if the operation was canceled, accessing the Result property raises an exception.
关于c# - BackgroundWorker OnWorkCompleted 抛出跨线程异常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/818767/