C# BackgroundWorker OnWorkCompleted引发跨线程异常
我有一个用于数据库分页的简单UserControl,它使用一个控制器来执行实际的DAL调用。我使用C# BackgroundWorker OnWorkCompleted引发跨线程异常,c#,winforms,backgroundworker,arcgis,multithreading,C#,Winforms,Backgroundworker,Arcgis,Multithreading,我有一个用于数据库分页的简单UserControl,它使用一个控制器来执行实际的DAL调用。我使用BackgroundWorker执行重载,在OnWorkCompleted事件中,我重新启用一些按钮,更改TextBox.Text属性,并为父窗体引发事件 表单A保存我的用户控件。当我单击打开表单B的某个按钮时,即使我没有在“那里”执行任何操作并将其关闭,然后尝试从数据库中引入下一页,工作线程(而不是主线程)会调用OnWorkCompleted,并抛出跨线程异常 目前,我在那里的处理程序中添加了一个
BackgroundWorker
执行重载,在OnWorkCompleted
事件中,我重新启用一些按钮,更改TextBox.Text属性,并为父窗体引发事件
表单A保存我的用户控件。当我单击打开表单B的某个按钮时,即使我没有在“那里”执行任何操作并将其关闭,然后尝试从数据库中引入下一页,工作线程(而不是主线程)会调用OnWorkCompleted
,并抛出跨线程异常
目前,我在那里的处理程序中添加了一个对invokererequired
的检查,但是OnWorkCompleted
的全部要点不是要在主线程上调用吗?为什么它不能像预期的那样工作
编辑:
我已设法将问题缩小到arcgis和BackgroundWorker
。我有下面的解决方案,它在arcmap中添加了一个命令,可以用两个按钮打开一个简单的Form1
第一个按钮运行一个BackgroundWorker
,该按钮睡眠500毫秒并更新计数器。
在RunWorkerCompleted
方法中,它检查invokererequired
,并更新标题以显示该方法最初是在主线程还是工作线程内运行的。
第二个按钮只打开Form2
,其中不包含任何内容
首先,对RunWorkerComplete
的所有调用都是在主线程内进行的(正如预期的那样-这是RunWorkerComplete方法的整体要点,至少从我对BackgroundWorker
的了解来看是如此)
在打开和关闭Form2
之后,总是在工作线程上调用RunWorkerCompleted
。我想补充一点,我可以将此解决方案保持原样(在RunWorkerCompleted
方法中检查invokererequired
),但我想了解为什么它会发生在我的预期之外。在我的“真实”代码中,我希望始终知道在主线程上调用RunWorkerCompleted
方法
我设法将问题定位在表单.Show()上我的BackgroundTesterBtn
中的code>命令-如果我改用ShowDialog()
,我不会遇到问题(RunWorkerCompleted
始终在主线程上运行)。我确实需要在我的ArcMap项目中使用Show()
,这样用户就不会被绑定到表单
我还试图在一个普通的WinForms项目上重现这个bug。我添加了一个简单的项目,在没有ArcMap的情况下只打开第一个表单,但在这种情况下,我无法重现错误-无论我在打开Form2
之前还是之后使用Show()
还是ShowDialog()
,都会在主线程上运行RunWorkerCompleted
。我尝试在我的表单1
之前添加第三个表单作为主表单,但结果没有改变
是我的简单sln(VS2005sp1)-它需要
ESRI.ArcGIS.ADF(9.2.4.1420)
ESRI.ArcGIS.ArcMapUI(9.2.3.1380)
ESRI.ArcGIS.SystemUI(9.2.3.1380)
OnWorkCompleted
的整个要点不是要在主线程上调用吗?为什么它不能像预期的那样工作
不,不是。
你不能在任何旧线程上运行任何旧东西。线程不是礼貌的对象,你可以简单地说“请运行这个”
一个更好的思维模型是一列货运火车。一旦开始,它就会偏离自己的轨道。你不能改变或阻止它。如果你想影响它,你要么等到它到达下一个火车站(例如:让它手动检查一些事件),要么让它出轨(Thread.Abort
和CrossThread异常的后果与让火车出轨差不多……小心!)
Winforms控件某种程度上支持这种行为(它们有Control.BeginInvoke
,它允许您在UI线程上运行任何函数),但这只起作用,因为它们在windows UI消息泵中有一个特殊的挂钩,并编写一些特殊的处理程序。按照上面的类比,他们的列车在车站检票,并定期寻找新的方向,你可以使用该设施发布你自己的方向
BackgroundWorker
是为通用而设计的(它不能绑定到windows GUI),因此它不能使用windows控件。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
}
在访问结果属性之前,RunWorkerCompleted事件处理程序应始终检查错误和取消属性。如果引发了异常或操作被取消,则访问Result属性会引发异常
BackgroundWorker
检查委托实例是否指向支持接口ISynchronizeInvoke
的类。DAL层可能没有实现该接口。通常,您会在表单
上使用BackgroundWorker
,该表单确实支持该接口
如果您想从DAL层使用BackgroundWorker
,并想从那里更新UI,您有三个选项:
- 您将继续调用
Invoke
方法
- 在DAL类上实现接口ISynchronizeInvoke,并手动重定向调用(只有三个方法和一个属性)
- 在调用
BackgroundWorker之前
if(control.InvokeRequired)
control.Invoke(Action);
else
Action()