VB.net ApplicationFramework plus SplashScreen: InvalidOperationException
-
13-10-2019 - |
سؤال
I recently changed my app from using a custom SplashScreen (it was just a Form with a Timer loaded the main form and closed itself) to the Application Framework.
Here is what I did:
- Created a new SplashScreenForm that shows the app version etc.
- Selected that Form at: My Project -> Application -> SplashScreen
- Moved long running initialisation code from the constructor of the main form to the ApplicationEvents Startup Event
That totally does what I want. The SplashScreen shows up first, than the Startup Event fires and does it's work. The SplashScreen closes and the actual main forms shows up.
So far so good. But our customers sometimes get this nasty exception during Startup:
System.InvalidOperationException: Invoke oder BeginInvoke kann für ein Steuerelement erst aufgerufen werden, wenn das Fensterhandle erstellt wurde.
bei System.Windows.Forms.Control.WaitForWaitHandle(WaitHandle waitHandle)
bei System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous)
bei System.Windows.Forms.Control.Invoke(Delegate method, Object[] args)
bei System.Windows.Forms.Control.Invoke(Delegate method)
bei Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase.HideSplashScreen()
bei Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase.MainFormLoadingDone(Object sender, EventArgs e)
bei System.EventHandler.Invoke(Object sender, EventArgs e)
bei System.Windows.Forms.Form.OnLoad(EventArgs e)
bei System.Windows.Forms.Form.OnCreateControl()
bei System.Windows.Forms.Control.CreateControl(Boolean fIgnoreVisible)
bei System.Windows.Forms.Control.CreateControl()
bei System.Windows.Forms.Control.WmShowWindow(Message& m)
bei System.Windows.Forms.Control.WndProc(Message& m)
bei System.Windows.Forms.ScrollableControl.WndProc(Message& m)
bei System.Windows.Forms.Form.WmShowWindow(Message& m)
bei System.Windows.Forms.Form.WndProc(Message& m)
bei System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
bei System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
bei System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
There seems to be an error during HideSplashScreen(), but the thing is that the whole stack is out of my control so I can't just catch this exception.
Any suggestions?
المحلول
Put this in your splash from. If there are some components, this sub might be already declared in Designer.vb file. Simply move it's content to your source code and insert first line.
Protected Overrides Sub Dispose(ByVal disposing As Boolean)
If disposing Then My.Application.SplashScreen = Nothing
MyBase.Dispose(disposing)
End Sub
I've run throught deep analyse including decompilation of framework assemblies and this should do the trick. Detailed explanation would be longer story.
I'm unable to reproduce this issue on my own computer, but error occurs on various client machines. It seems impossible to reproduce it even with malicious calls. There's no HW nor SW related condition to simulate the issue, but it usually occurs on slow or high CPU used PCs when event loop messages got delayed and working threads are switched in wrong moment.
نصائح أخرى
- Are you able to reproduce the problem (i.e. distinguish certain scenarios where the problem does and does not occur
- What events are you handling in the
MyApplication class
? - Try to add the following handler in that class, maybe the exception will be caught here, and it will supply you more info about stack
- When you reach the handler, check if there are
InnerException
s recursively and determine if the error you see is indeed the source error, or just a rethrow. - Disable "Just My Code" (read this for details) and you might be able to trace the source of the issue
HTH
Private Sub MyApplication_UnhandledException(
ByVal sender As Object,
ByVal e As ApplicationServices.UnhandledExceptionEventArgs) _
Handles Me.UnhandledException
Stop
End Sub
This is my solution. I ended with just swallowing any InvalidOperationException in the UnhandledException Event until the SplashScreen is gone.
To do that I added a Property MainFormLoadingComplete
to my MyApplication class that is set to true in the Shown Event of my main form (the splashscreen stays until the Form_Load event is processed).
I also figured out that I have to set MinimumSplashScreenDisplayTime
before OnInitialize()
to be effective. Hopefully that helps avoiding the exception in the first place. But since this exception is totally random I
SplashScreen code:
Public Class SplashScreen
Private Sub SplashScreen_Disposed(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Disposed
' Simulate InvalidOperationException
Throw New InvalidOperationException
End Sub
End Class
Form1 code:
Public Class Form1
Private Sub Form1_Shown(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Shown
My.MyApplication.MainFormLoadingComplete = True
End Sub
End Class
MyApplication code:
Partial Friend Class MyApplication
Public Shared Property MainFormLoadingComplete As Boolean
Protected Overrides Function OnInitialize(ByVal commandLineArgs As System.Collections.ObjectModel.ReadOnlyCollection(Of String)) As Boolean
' MinimumSplashScreenDisplayTime has to be set before OnInitialize to be effective
MinimumSplashScreenDisplayTime = 3000
Return MyBase.OnInitialize(commandLineArgs)
End Function
Private Sub MyApplication_Startup( _
ByVal sender As Object, ByVal e As Microsoft.VisualBasic.ApplicationServices.StartupEventArgs) Handles Me.Startup
' Simulate application init process
System.Threading.Thread.Sleep(5000)
End Sub
Private Sub MyApplication_UnhandledException(ByVal sender As Object, ByVal e As Microsoft.VisualBasic.ApplicationServices.UnhandledExceptionEventArgs) Handles Me.UnhandledException
' Swallow InvalidOperationException if the MainForm has not been shown
If MainFormLoadingComplete = False AndAlso IsCausedByHideSplashScreen(e.Exception) Then
' Logging stuff...
' Prevent application exit
e.ExitApplication = False
End If
End Sub
Private Function IsCausedByHideSplashScreen(ByVal ex As Exception) As Boolean
If ex.GetType Is GetType(InvalidOperationException) Then
Return New StackTrace(ex).GetFrames().Count(Function(x) x.GetMethod().Name = "HideSplashScreen") > 0
Else
Return False
End If
End Function
End Class
just came across this, we have the exact same problem using the same technique.
I'm going to try your solution that works but I just wanted to let you know how to reproduce it:
Our support people spent a lot of time on this and finally narrowed it down to happening only in windows 7 and only happening just after windows has been started from boot.
If you reboot windows then immediately launch your app that uses this splash screen technique the error will happen almost all the time.
If you reboot windows, wait a few minutes then start your app you will never see the error.
I worked around it by putting in a sleep(400) at the bottom of my main form's load event, however some were still seeing the error and I'm going to try your solution instead.