从CefSharp加载的Winform控件导致跨线程错误

时间:2017-03-14 17:12:09

标签: vb.net winforms cefsharp

我正在将CefSharp集成到传统的Winforms应用程序中。目前,该应用程序使用默认的.NET浏览器控件。不幸的是,这种控制存在内存泄漏,导致严重问题。我试图在没有重大重构的情况下集成CefSharp浏览器,因为该应用程序计划在今年晚些时候退役,并替换为新的WPF应用程序。

到目前为止,我能够在大多数情况下使CefSharp工作。标准网页打开没有问题。但是,其中一些页面具有特殊形成的链接,当应用程序解释时,它们打开.Net表单而不是其他网页。这是我遇到问题的地方。当在CefSharp中打开的网页调用其中一个链接并且应用程序尝试打开新表单时,它似乎是在托管CefSharp实例的线程上执行此操作,这与托管DocumentTabStrip实例的线程不同申请本身。这导致了许多跨线程问题(所讨论的遗留应用程序没有特别好的架构)。我正试图找到一种方法来解决这个问题而无需重新构建Winform应用程序

以下是该情况的简要示例。

控制

frmMain

这是应用程序的主要表单。它有许多职责,但与当前情况相关的是它托管一个Telerik DocumentTabStrip,其中包含应用程序的“标签”(每个浏览器或表单在其中一个标签中打开)。它还包含用于加载实例化的各种表单或浏览器控件的方法,并将它们添加到前面提到的CefSharp

ucChromeBrowser

这是包装创建的CefSharp浏览器实例的对象。它还包含IRequestHandler个事件的所有事件处理程序,例如ILifespanHandlerIContextMenuHandlerucChromeBrowser等。

EntryScreens.uc_Foo

这是从WEB2WIN:Entryscreens.uc_Foo?AdditionalParameterDataHere中托管的网页调用的Winform控件之一。典型链接看起来像frmMain。我们在EntryScreen.uc_Foo中截取这些调用,而不是尝试在浏览器中打开链接,我们实例化frmMain.DocumentTabStrip的新实例并将该新表单加载到Dim _DockWindow As Telerik.WinControls.UI.Docking.DocumentWindow = Nothing Dim _Control As ucBaseControl = Nothing Dim _WebBrowser As ucBrowser = Nothing Dim _isWebBrowerLink As Boolean = False 'Do Other Stuff here, such as instantiating the _Control or _WebBrowser instance, setting _isWebBrowserLink, etc. _DockWindow = New Telerik.WinControls.UI.Docking.DocumentWindow If _isWebBrowerLink Then If Not IsNothing(_WebBrowser) Then _WebBrowser.Dock = DockStyle.Fill _DockWindow.Controls.Add(_WebBrowser) End If Else _Control.Dock = DockStyle.Fill _DockWindow.Controls.Add(_Control) End If DocumentTabStrip.InvokeIfRequired(Sub() DocumentTabStrip.Controls.Add(_DockWindow)) ,如下所示。

InvokeIfRequired

(如果重要,这是我正在调用的Public Module ISynchronizeInvokeExtensions <Runtime.CompilerServices.Extension> Public Sub InvokeIfRequired(obj As ISynchronizeInvoke, action As MethodInvoker) Dim idleCounter As Integer = 0 While Not CType(obj, Control).Visible 'Attempt to sleep since there's no visible control If idleCounter < 5 Then Threading.Thread.Sleep(50) Else Exit While End If End While If obj.InvokeRequired Then Dim args = New Object(-1) {} obj.Invoke(action, args) Else action() End If End Sub End Module 方法。)

DocumentTabStrip.InvokeIfRequired(Sub() DocumentTabStrip.Controls.Add(_DockWindow))

尝试拨打CefSharp时会出现此问题。据我所知,这似乎改变了托管在其中的控件的布局,导致各种控件被实例化或调用其事件。反过来,这会导致InvalidOperationException(例如:“跨线程操作无效:控制'pnlLoading'从其创建的线程以外的线程访问。”)。特定的子控件因Form而异(因此对于Form A,它可能始终是pnlLoading导致它,但对于另一个Form,它可能是一个不同的控件)。但是大多数或全部都表现出这种行为。我毫不怀疑这是由于控制本身设计不佳,但我没有时间重构所有这些。

这就是情况。似乎hostname的多线程特性与所讨论的控件的单线程特性相冲突,并导致它们在不同的线程上被调用。有办法防止这种情况吗?

1 个答案:

答案 0 :(得分:1)

控件应仅在UI线程上创建,而不是在后台线程中创建。错误消息非常清楚地告诉您发生了什么。

  

跨线程操作无效:控制&#39; pnlLoading&#39;从线程访问,而不是在上创建的线程。

您正在从UI线程访问该控件,但由于它实际上是在后台线程中创建的,因此您正在执行跨线程访问,因为您正在调用错误的线程。

无论你做什么,你几乎总是必须在访问控件时调用后台线程,但是你不能通过例如UI消息循环完成任何自动访问。

因此,您应该仅在UI中创建和访问所有控件,这意味着您必须将所有代码放在第一个代码块中的调用中。

DocumentTabStrip.InvokeIfRequired( _
    Sub()

        Dim _DockWindow As Telerik.WinControls.UI.Docking.DocumentWindow = Nothing
        Dim _Control As ucBaseControl = Nothing
        Dim _WebBrowser As ucBrowser = Nothing
        Dim _isWebBrowerLink As Boolean = False
        'Do Other Stuff here, such as instantiating the _Control or _WebBrowser instance, setting _isWebBrowserLink, etc.
        _DockWindow = New Telerik.WinControls.UI.Docking.DocumentWindow
        If _isWebBrowerLink Then
            If Not IsNothing(_WebBrowser) Then
                _WebBrowser.Dock = DockStyle.Fill
                _DockWindow.Controls.Add(_WebBrowser)
            End If
        Else
            _Control.Dock = DockStyle.Fill
            _DockWindow.Controls.Add(_Control)
        End If

        DocumentTabStrip.Controls.Add(_DockWindow)

    End Sub)
相关问题