VB.net ApplicationFramework plus SplashScreen: InvalidOperationException
Asked Answered
F

4

6

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?

Formulaic answered 28/12, 2010 at 8:8 Comment(5)
Are you able to reproduce the problem?Ramtil
Any update on this issue? I'm still getting the same problem. Thanks.Swaziland
I added this line to the Beginning of my ApplicationStartup Event: MinimumSplashScreenDisplayTime = 3000 and so far I haven't had this exception. But since this exeception is totally random I can't say for sure if that helped.Thury
The only 'sure' fix I found was to: 1. Clear the application splash screen in the project settings, manually 2. .Show() it in MyApplication_Startup and 3. Close it when the startup logic completesDesrochers
The solution from Pavlinll works pretty well. Did not have any evidence of that exception since I implemented his code.Thury
M
5

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.

Macaco answered 27/6, 2012 at 15:40 Comment(5)
Thanks, I will give it a try. Since I did some clever error handling for this, Clients will never notice this exception. But on my developer machine this happens once a week or two (even while debugging).Thury
Update: Since I updated my codebase, this error never happend again (at least during debugging on my dev machine). Looks good so far.Thury
Unfortunatelly, there still exists small chance for this exception to happen. There's Invoke call to Dispose method in HideSplashScreen and that's generally bad practice. Because when enqueued invoke gets fired too fast the control is disposed and consequent WaitForWaitHandle might throw an exception in this situation. This can't be fixed from code and will occur propably on single core CPU when threads are switched in unsuitable moment.Macaco
I'm goint to put Threading.Thread.Sleep(200) before MyBase.Dispose(disposing). That should provide enough time to WaitForWaitHandle to start waiting. It's an ugly workaround. Good solution would be to reset SplashScreen as soon as possible and dispose the form manually. Please vote: connect.microsoft.com/VisualStudio/feedback/details/769497/…Macaco
I voted. You should link this thread in your Microsoft Connect post.Thury
R
1
  1. Are you able to reproduce the problem (i.e. distinguish certain scenarios where the problem does and does not occur
  2. What events are you handling in the MyApplication class?
  3. 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
  4. When you reach the handler, check if there are InnerExceptions recursively and determine if the error you see is indeed the source error, or just a rethrow.
  5. 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
Ramtil answered 28/12, 2010 at 8:29 Comment(5)
+1 for suggestion number 4 - very interesting, thx. I already handle the UnhandledException to show a custom error screen. And the only other event is the Startup Event. The error is not reproducable because It only occured completly random a couple of times on client computers. Maybe I should give out a debug build first and hope the stack trace will be more verbose.Thury
Did it happen in your computer as well? Maybe you can know according to the OS of those unique clients (maybe all of them are x64/server/whatever), or does it happen in ALL computers?Ramtil
On the custom error, try to figure out if there is InnerException in the main oneRamtil
It isn't. My UnhandledException handler loops over every inner exception and logs everything it can find, nothing there. I just had a theory: My init code ususally takes 2 or 3 secs. I suppose that after the Startup Event finishes the SplashScreen is automatically closed. Maybe on fast machines the init is done after the SplashScreen is created but before it is shown. Now the framework calls HideSplashScreen() and ran's into that exception. Maybe I should try to add a Thread.Sleep(1000) if the init is too fast.Thury
You could also try to call Thread.Sleep from the splash screen, or try to explicitly throw an exception from the splash screen. It's really hard to tell since we cannot reproduce the problem, nor do you have a debugging tool on the problematic machine. I feel your pain manRamtil
F
1

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
Formulaic answered 29/1, 2011 at 14:20 Comment(0)
P
-1

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.

Pournaras answered 20/4, 2011 at 16:2 Comment(1)
I also figured out that this exceptions seems to be somehow related to the form load event (even if it doesn't tell you in the stacktrace), since not all code from the load event executes (in my case I load a usercontrol with a login box that is never shown after the exception occures even with my workaround. So I moved most of the code to the Shown event.Thury

© 2022 - 2024 — McMap. All rights reserved.