I'm integrating CefSharp
into a legacy Winforms application. Currently the application uses the default .NET browser control. Unfortunately, this control has a memory leak which is causing serious problems. I'm trying to get the CefSharp
browser integrated without major refactoring, since the application is scheduled to be retired later this year and replaced with a new WPF application.
So far, I'm able to make CefSharp
work in most scenarios. Standard webpages open without a problem. However, some of these pages have specially formed links which, when interpretted by the application, open up .Net forms instead of other webpages. This is where I run into an issue. When the webpage opened in CefSharp
calls one of these links and the application attempts to open the new form, it appears to be doing so on the thread hosting the CefSharp
instance, which is different from that hosting the application itself. This leads to numerous cross-thread issues (the legacy application in question isn't particularly well architectured). I'm trying to find a way to solve this issue without rearchitecturing the Winform application.
The following is a brief example of the situation.
Controls
frmMain
This is the primary form on the application. It has a number of duties, but the one pertinent to the current situation is that it hosts a Telerik DocumentTabStrip
which contains the application's "tabs" (each browser or form opens within one of these tabs). It also contains the method that is used to load the various form or browser controls that are instantiated and add them to the aforementioned DocumentTabStrip
.
ucChromeBrowser
This is the object which wraps the CefSharp
browser instances which get created. It also has all the Event Handlers for the CefSharp
events, such as IRequestHandler
, ILifespanHandler
, IContextMenuHandler
, etc.
EntryScreens.uc_Foo
This is one of the Winform controls that are called from the webpage hosted in ucChromeBrowser
. A typical link looks like WEB2WIN:Entryscreens.uc_Foo?AdditionalParameterDataHere
. We intercept these calls in frmMain
and instead of attempting to open the link in a browser, we instantiate a new instance of EntryScreen.uc_Foo
and load that new form into frmMain.DocumentTabStrip
as follows.
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))
(In case it matters, here's the InvokeIfRequired
method I'm calling.)
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
The issue comes up when trying to call DocumentTabStrip.InvokeIfRequired(Sub() DocumentTabStrip.Controls.Add(_DockWindow))
. From what I can tell, it appears that this changes the layout of the controls hosted within, causing various controls to be instantiated or to have their events called. This, in turn, causes an InvalidOperationException (e.g.: "Cross-thread operation not valid: Control 'pnlLoading' accessed from a thread other than the thread it was created on."). The specific child control varies from Form to Form (so for Form A it might always be pnlLoading which causes it, but for another Form it might be a different control). But most or all of them exhibit this behavior. I have no doubt it's due to the controls themselves being poorly designed, but I don't really have the time to refactor all of them.
So that's the situation. It appears as though the multi-threaded nature of CefSharp
is conflicting with the single-threaded nature of the controls in question and causing them to get called on a different thread than they would be otherwise. Is there a way to prevent this?