c# - BackgroundWorker OnWorkCompleted 抛出跨线程异常

标签 c# winforms backgroundworker arcgis multithreading

我有一个用于数据库分页的简单用户控件,它使用一个 Controller 来执行实际的 DAL 调用。我用 BackgroundWorker执行繁重的工作,并在 OnWorkCompleted 上事件我重新启用一些按钮,更改 TextBox.Text属性并为父窗体引发事件。

Form A 包含我的 UserControl。当我单击某个打开表单 B 的按钮时,即使我没有在“那里”做任何事情而只是关闭它,并尝试从我的数据库中引入下一页,OnWorkCompleted在工作线程(而不是我的主线程)上被调用,并抛出跨线程异常。

此刻我添加了一个 InvokeRequired 的支票在处理程序那里,但这不是 OnWorkCompleted 的重点是在主线程上调用?为什么它不能按预期工作?

编辑:

我已经设法将问题缩小到 arcgis 和 BackgroundWorker .我有以下解决方案,它向 arcmap 添加了一个命令,它打开了一个简单的 Form1有两个按钮。

第一个按钮运行 BackgroundWorker休眠 500 毫秒并更新计数器。 在RunWorkerCompleted它检查 InvokeRequired 的方法,并更新标题以显示该方法最初是在主线程还是工作线程中运行。 第二个按钮刚打开Form2 , 什么都不包含。

一开始,所有对RunWorkerCompletedare的调用是在主线程内创建的(正如预期的那样 - 这就是 RunWorkerComplete 方法的重点,至少根据我从 MSDNBackgroundWorker 的了解)

开闭后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
}

Also, don't forget this:

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/

相关文章:

c# - 从数据库中获取具有特定列的数据 MVC4

c# - 将 DLL 构建到单独的文件夹

c# - 如何将对象从 form1 传递到 form2 再返回到 form1?

c# - 如何在设计时将图像设置为窗口窗体中的超链接

vb.net - 如何取消一个非迭代的BackGroundWorker

C#,后台 worker 类

c# - 如何从ms word文档的当前可见页面获取单词?

c# - C#中有 "try to lock, skip if timed out"操作吗?

c# - 在 C# 客户端中为 gRPC 设置 keepalive

C# 从BackgroundWorker 更新DataGridView