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

表单 A 保存我的用户控件。当我单击某个打开表单 B 的按钮时,即使我“那里”没有执行任何操作并只是将其关闭,并尝试从我的数据库中引入下一页, OnWorkCompleted 在工作线程(而不是我的主线程)上调用,并抛出跨线程异常。

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

编辑:

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

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

首先,所有的调用 RunWorkerCompletedare 是在主线程内制作的(正如预期的那样 - 这就是 RunWorkerComplete 方法的重点,至少根据我从 微软软件定义网络BackgroundWorker)

打开和关闭后 Form2, , 这 RunWorkerCompleted 总是在工作线程上被调用。我想补充一点,我可以按原样保留这个问题的解决方案(检查 InvokeRequired 在里面 RunWorkerCompleted 方法),但我想了解为什么它会违背我的预期。在我的“真实”代码中,我想始终知道 RunWorkerCompleted 方法正在主线程上被调用。

我设法找出问题所在 form.Show(); 命令在我的 BackgroundTesterBtn - 如果我使用 ShowDialog() 相反,我没有遇到任何问题(RunWorkerCompleted 始终在主线程上运行)。我确实需要使用 Show() 在我的ArcMap项目中,这样用户就不会被绑定到表单。

我还尝试在正常的 WinForms 项目上重现该错误。我添加了一个简单的项目,只在没有 ArcMap 的情况下打开第一个表单,但在这种情况下我无法重现该错误 - RunWorkerCompleted 在主线程上运行,无论我使用 Show() 或者 ShowDialog(), ,打开前和打开后 Form2. 。我尝试在我的之前添加第三个表单作为主表单 Form1, ,但这并没有改变结果。

这里 是我的简单 sln (VS2005sp1) - 它需要

ESRI.ArcGIS.ADF(9.2.4.1420)

ESRI.ArcGIS.ArcMapUI(9.2.3.1380)

ESRI.ArcGIS.SystemUI (9.2.3.1380)

有帮助吗?

解决方案

看起来像个错误:

http://connect.microsoft.com/VisualStudio/feedback /ViewFeedback.aspx?FeedbackID=116930

http://thedatafarm.com/devlifeblog/archive/2005 /12/21/39532.aspx

所以我建议使用防弹(伪代码):

if(control.InvokeRequired)
  control.Invoke(Action);
else
  Action()

其他提示

  

是不是要在主线程上调用 OnWorkCompleted 的重点?为什么不按预期工作?

不,不是。
你不能在任何旧线程上运行任何旧东西。线程不是礼貌的对象,你可以简单地说“运行这个,请”。

线程的更好的心理模型是货运列车。一旦它开始,它就在它自己的轨道上。你无法改变它的路线或阻止它。如果你想要影响它,你要么必须等到它到达下一个火车站(例如:让它手动检查一些事件),或者让它脱轨( Thread.Abort 和CrossThread异常有与出轨火车相同的后果......小心!)。

Winforms控件排序支持这种行为(他们有 Control.BeginInvoke ,它允许你在UI线程上运行任何函数),但这只能起作用,因为他们有一个特殊挂钩进入windows UI消息泵并编写一些特殊的处理程序。按照上述类比,他们的火车在车站办理登机手续并定期查找新方向,您可以使用该设施发布自己的指示。

BackgroundWorker 旨在用于通用(它不能绑定到Windows GUI),因此它不能使用windows Control.BeginInvoke 功能。它必须假设你的主线程是一个不可阻挡的“火车”做它自己的事情,所以完成的事件必须在工作线程中运行或根本不运行。

但是,当您使用winforms时,在 OnWorkCompleted 处理程序中,您可以使用 BeginInvoke 让Window执行另一个回调我上面提到过的功能。像这样:

// 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
}

此外,请不要忘记:

  

在访问Result属性之前,RunWorkerCompleted事件处理程序应始终检查Error和Cancelled属性。如果引发了异常或操作被取消,则访问Result属性会引发异常。

BackgroundWorker 检查委托实例是否指向支持该接口的类 ISynchronizeInvoke. 。您的 DAL 层可能没有实现该接口。通常,您会使用 BackgroundWorker 在一个 Form, ,它确实支持该接口。

如果您想使用 BackgroundWorker 从 DAL 层并想要从那里更新 UI,您有三个选择:

  • 你会继续打电话 Invoke 方法
  • 实现接口 ISynchronizeInvoke 在 DAL 类上,并手动重定向调用(只有三个方法和一个属性)
  • 在调用之前 BackgroundWorker (所以,在 UI 线程上),调用 SynchronizationContext.Current 并将内容实例保存在实例变量中。这 SynchronizationContext 然后会给你 Send 方法,它会做什么 Invoke 做。
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top