Hi,
I have a multithreaded WinForms application, and there are multiple windows that run on their own message pump threads, that is, the window is created with code like this (start in NewTI):
Public Class LaunchSettings
Public Self As FUIFaultTable
Public Title As String
End Class
Public Shared Function NewTI(Optional ByVal ATitle As String = "Console") As FUIFaultTable
Dim tthread As System.Threading.Thread = New System.Threading.Thread(New System.Threading.ParameterizedThreadStart(AddressOf TIStart))
Dim tsettings As New LaunchSettings
tsettings.Title = ATitle
tthread.Start(tsettings)
Threading.Thread.Sleep(100)
SyncLock tsettings
Threading.Monitor.Wait(tsettings, 5000)
End SyncLock
NewTI = tsettings.Self
End Function
Private Shared Sub TIStart(ByVal ASettings As LaunchSettings)
Try
SyncLock ASettings
ASettings.Self = New FUIFaultTable
Threading.Monitor.Pulse(ASettings)
End SyncLock
ASettings.Self.Text = ASettings.Title
Application.Run(ASettings.Self)
Catch ex As Exception
X.AssertSoft(False, "Unhandled Console error", Nothing, , ex)
End Try
End Sub
In other words, the calling threads creates a new thread, which runs TIStart method, which runs the message pump in Application.Run, which exits only when the window is closed. There is also a simple notification to the calling thread that the window creation process in complete.
This code is compliant with Microsoft’s guidelines, and all the methods within this form are suitably protected against cross-threaded class by InvokeRequired() ... BeginInvoke() constructs, again as prescribed by MS.
Now to the problem: when Elegant.UI components (not necessarily the Ribbon, but for example a ToggleButton) are placed on this form, Application.Run throws an occasional exception with the following stack trace (always the same, always on the message pump thread, bounces out of Application.Run in the code snippet above):
at System.Windows.Forms.Control.get_Handle()
at System.Windows.Forms.Control.PointToClientInternal(Point p)
at System.Windows.Forms.Control.PointToClient(Point p)
at Elegant.Ui.Control.PointToClient(IControlInternal control, Point point)
at Elegant.Ui.Control.OnPreviewMouseLeave(PreviewEventArgs e)
at Elegant.Ui.Control.Elegant.Ui.IControlInternal.InvokePreviewMouseLeave(PreviewEventArgs e)
at Elegant.Ui.Control.TunnelPreviewEvent(IControlInternal control, PreviewEventType eventType)
at Elegant.Ui.Control.OnMouseMove(IControlInternal control, MouseEventArgs e)
at Elegant.Ui.Control.OnMouseMove(MouseEventArgs e)
at System.Windows.Forms.Control.WmMouseMove(Message& m)
at System.Windows.Forms.Control.WndProc(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
This happens infrequently, but regularly enough, when mouse is moved over the controls. Frequency tends to be the highest when the window is being closed, for example if the "X" button is clicked and the mouse is accidentally moved over one of Elegant controls. Same for application shutdown, often times an exception will be thrown around the time when Form.Close is called. Overlapped windows increase the probability.
It appears that, somehow, a mouse move event from one message pump/window is passed onto another window (which runs on a different message pump thread), and then the recipient window tries to process it, and that’s when the form throws the exception. I know of no way that the form that is running on the same message pump can throw this exception.
And lastly, to answer the question as to why do we use multiple message pumps: we have a soft real-time application that regularly spikes the CPU. Lesser important GUIs are delegated to lower priority threads, that way the critical GUI elements do not get queued up behind long updates, such as table redraws.
Thank you in advance,
-Alex