从 CefSharp 加载的 Winform 控件导致跨线程错误
Winform controls loaded from CefSharp causing Cross-Thread errors
我正在将 CefSharp
集成到旧版 Winforms 应用程序中。目前,该应用程序使用默认的 .NET 浏览器控件。不幸的是,此控件存在内存泄漏,这会导致严重的问题。我正在尝试在不进行重大重构的情况下集成 CefSharp
浏览器,因为该应用程序计划于今年晚些时候停用并替换为新的 WPF 应用程序。
到目前为止,我能够让 CefSharp
在大多数情况下工作。标准网页打开没有问题。但是,其中一些页面具有特殊格式的 link,当由应用程序解释时,它们会打开 .Net 表单而不是其他网页。这是我 运行 遇到的问题。当在 CefSharp
中打开的网页调用这些 link 之一并且应用程序尝试打开新表单时,它似乎是在托管 CefSharp
实例的线程上这样做的,它是不同于托管应用程序本身。这会导致许多跨线程问题(有问题的遗留应用程序的架构不是特别好)。我正在尝试找到一种方法来解决这个问题 而无需重新构建 Winform 应用程序。
以下是情况的简要示例。
控件
frmMain
这是申请表的主要形式。它有许多职责,但与当前情况相关的一个是它托管一个 Telerik DocumentTabStrip
,其中包含应用程序的 "tabs"(每个浏览器或表单都在这些选项卡之一中打开)。它还包含用于加载实例化的各种窗体或浏览器控件并将它们添加到上述 DocumentTabStrip
.
的方法
ucChrome浏览器
这是包装创建的 CefSharp
浏览器实例的对象。它还具有 CefSharp
事件的所有事件处理程序,例如 IRequestHandler
、ILifespanHandler
、IContextMenuHandler
等
EntryScreens.uc_Foo
这是从 ucChromeBrowser
中托管的网页调用的 Winform 控件之一。典型的 link 看起来像 WEB2WIN:Entryscreens.uc_Foo?AdditionalParameterDataHere
。我们在 frmMain
中拦截这些调用,而不是尝试在浏览器中打开 link,我们实例化 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))
时出现问题。据我所知,这似乎改变了其中托管的控件的布局,导致各种控件被实例化或调用它们的事件。这反过来会导致 InvalidOperationException(例如:"Cross-thread operation not valid: Control 'pnlLoading' accessed from a thread other than the thread it was created on.")。特定的子控件因窗体而异(因此对于窗体 A,它可能总是导致它的 pnlLoading,但对于另一个窗体,它可能是不同的控件)。但大多数或全部都表现出这种行为。我毫不怀疑这是由于控件本身设计不当,但我真的没有时间重构所有控件。
情况就是这样。 CefSharp
的多线程特性似乎与所讨论控件的单线程特性相冲突,并导致它们在与其他情况不同的线程上被调用。有办法防止这种情况吗?
控件只能在 UI 线程上创建,而不是在后台线程中创建。错误消息非常清楚地告诉您发生了什么。
Cross-thread operation not valid: Control 'pnlLoading' accessed from a thread other than the thread it was created on.
您正在从 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)
我正在将 CefSharp
集成到旧版 Winforms 应用程序中。目前,该应用程序使用默认的 .NET 浏览器控件。不幸的是,此控件存在内存泄漏,这会导致严重的问题。我正在尝试在不进行重大重构的情况下集成 CefSharp
浏览器,因为该应用程序计划于今年晚些时候停用并替换为新的 WPF 应用程序。
到目前为止,我能够让 CefSharp
在大多数情况下工作。标准网页打开没有问题。但是,其中一些页面具有特殊格式的 link,当由应用程序解释时,它们会打开 .Net 表单而不是其他网页。这是我 运行 遇到的问题。当在 CefSharp
中打开的网页调用这些 link 之一并且应用程序尝试打开新表单时,它似乎是在托管 CefSharp
实例的线程上这样做的,它是不同于托管应用程序本身。这会导致许多跨线程问题(有问题的遗留应用程序的架构不是特别好)。我正在尝试找到一种方法来解决这个问题 而无需重新构建 Winform 应用程序。
以下是情况的简要示例。
控件
frmMain
这是申请表的主要形式。它有许多职责,但与当前情况相关的一个是它托管一个 Telerik DocumentTabStrip
,其中包含应用程序的 "tabs"(每个浏览器或表单都在这些选项卡之一中打开)。它还包含用于加载实例化的各种窗体或浏览器控件并将它们添加到上述 DocumentTabStrip
.
ucChrome浏览器
这是包装创建的 CefSharp
浏览器实例的对象。它还具有 CefSharp
事件的所有事件处理程序,例如 IRequestHandler
、ILifespanHandler
、IContextMenuHandler
等
EntryScreens.uc_Foo
这是从 ucChromeBrowser
中托管的网页调用的 Winform 控件之一。典型的 link 看起来像 WEB2WIN:Entryscreens.uc_Foo?AdditionalParameterDataHere
。我们在 frmMain
中拦截这些调用,而不是尝试在浏览器中打开 link,我们实例化 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))
时出现问题。据我所知,这似乎改变了其中托管的控件的布局,导致各种控件被实例化或调用它们的事件。这反过来会导致 InvalidOperationException(例如:"Cross-thread operation not valid: Control 'pnlLoading' accessed from a thread other than the thread it was created on.")。特定的子控件因窗体而异(因此对于窗体 A,它可能总是导致它的 pnlLoading,但对于另一个窗体,它可能是不同的控件)。但大多数或全部都表现出这种行为。我毫不怀疑这是由于控件本身设计不当,但我真的没有时间重构所有控件。
情况就是这样。 CefSharp
的多线程特性似乎与所讨论控件的单线程特性相冲突,并导致它们在与其他情况不同的线程上被调用。有办法防止这种情况吗?
控件只能在 UI 线程上创建,而不是在后台线程中创建。错误消息非常清楚地告诉您发生了什么。
Cross-thread operation not valid: Control 'pnlLoading' accessed from a thread other than the thread it was created on.
您正在从 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)