我正在将CefSharp
集成到传统的Winforms应用程序中。目前,该应用程序使用默认的.NET浏览器控件。不幸的是,这种控制存在内存泄漏,导致严重问题。我试图在没有重大重构的情况下集成CefSharp
浏览器,因为该应用程序计划在今年晚些时候退役,并替换为新的WPF应用程序。
到目前为止,我能够在大多数情况下使CefSharp
工作。标准网页打开没有问题。但是,其中一些页面具有特殊形成的链接,当应用程序解释时,它们打开.Net表单而不是其他网页。这是我遇到问题的地方。当在CefSharp
中打开的网页调用其中一个链接并且应用程序尝试打开新表单时,它似乎是在托管CefSharp
实例的线程上执行此操作,这与托管DocumentTabStrip
实例的线程不同申请本身。这导致了许多跨线程问题(所讨论的遗留应用程序没有特别好的架构)。我正试图找到一种方法来解决这个问题而无需重新构建Winform应用程序。
以下是该情况的简要示例。
这是应用程序的主要表单。它有许多职责,但与当前情况相关的是它托管一个Telerik DocumentTabStrip
,其中包含应用程序的“标签”(每个浏览器或表单在其中一个标签中打开)。它还包含用于加载实例化的各种表单或浏览器控件的方法,并将它们添加到前面提到的CefSharp
。
这是包装创建的CefSharp
浏览器实例的对象。它还包含IRequestHandler
个事件的所有事件处理程序,例如ILifespanHandler
,IContextMenuHandler
,ucChromeBrowser
等。
这是从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
的多线程特性与所讨论的控件的单线程特性相冲突,并导致它们在不同的线程上被调用。有办法防止这种情况吗?
答案 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)