【问题标题】:BackgroundWorker OnWorkCompleted throws cross-thread exceptionBackgroundWorker OnWorkCompleted 抛出跨线程异常
【发布时间】:2010-10-23 13:22:00
【问题描述】:

我有一个用于数据库分页的简单 UserControl,它使用控制器来执行实际的 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。我想补充一点,我可以让这个问题的解决方案保持原样(检查RunWorkerCompleted 方法中的InvokeRequired),但我想了解它为什么会出乎我的意料。在我的“真实”代码中,我想始终知道在主线程上调用了 RunWorkerCompleted 方法。

我设法在我的BackgroundTesterBtn 中的form.Show(); 命令上指出了问题——如果我改用ShowDialog(),我没有问题(RunWorkerCompleted 总是在主线程上运行)。我确实需要在我的 ArcMap 项目中使用Show(),这样用户就不会被绑定到表单。

我还尝试在正常的 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)

【问题讨论】:

    标签: c# winforms backgroundworker arcgis multithreading


    【解决方案1】:

    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
    }
    

    Also, don't forget this:

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

    【讨论】:

    • 在 BackgroundWorker 上检查 msdn 我发现我特别应该使用 RunWorkerCompleted 事件来处理表单。 “您必须注意不要在 DoWork 事件处理程序中操纵任何用户界面对象。相反,通过 ProgressChanged 和 RunWorkerCompleted 事件与用户界面进行通信。”另外 - 每次我尝试一个更简单的场景时,它都能正常工作并在主线程上被调用。就像那里的 msdn 示例一样,或者大部分时间在我的应用程序中。它类似于 www.developmentnow.com/g/36_2008_3_0_0_1056508/Update-GUI-from-RunWorkerCompleted.htm
    • 这很有趣...我用一个简单的后台工作程序创建了一个测试应用程序(控制台应用程序),它在工作线程中触发。也许 BackgroundWorker 构造函数有一些神奇的作用,如果它是在 UI 线程上创建的,它会改变它的行为??
    【解决方案2】:

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

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

    • 你会一直调用Invoke 方法
    • 在 DAL 类上实现接口ISynchronizeInvoke,并手动重定向调用(只有三个方法和一个属性)
    • 在调用BackgroundWorker(因此,在UI 线程上)之前,调用SynchronizationContext.Current 并将内容实例保存在实例变量中。然后SynchronizationContext 将为您提供Send 方法,该方法将与Invoke 完全相同。

    【讨论】:

    • 我在 UserControl(即 ISyncornizeInvoke)中使用 BackgroundWorker。我的分页控制器不知道它在工作线程中运行,也不在乎。它只是执行一个分页查询。异常从 OnWorkCompleted 事件在 UserControl 内部引发。
    【解决方案3】:

    避免 GUI 中的跨线程问题的最佳方法是 use SynchronizationContext

    【讨论】:

      【解决方案4】:

      它看起来像一个错误:

      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()
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-11-03
        • 2018-09-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多