VB.net应用程序框架加载屏幕:InvalidOperationException

6
我最近将我的应用从使用自定义闪屏屏幕(只是一个带有计时器的表单,加载主表单并关闭自身)改为使用应用程序框架。

这是我所做的:

  • 创建了一个新的SplashScreenForm,显示应用程序版本等信息。
  • 在“我的项目”->“应用程序”->“闪屏屏幕”中选择该表单
  • 将长时间运行的初始化代码从主表单的构造函数移到ApplicationEvents启动事件中

这完全符合我的要求。闪屏屏幕首先出现,然后启动事件触发并进行其工作。闪屏屏幕关闭,实际的主表单出现。

到目前为止一切都很好。但我们的客户有时会在启动时遇到这个令人讨厌的异常:

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)

似乎在HideSplashScreen()期间出现了错误,但问题是整个堆栈都不受我的控制,所以我不能捕获此异常。有什么建议吗?

你能够复现这个问题吗? - Shimmy Weitzhandler
这个问题有任何更新吗?我仍然遇到同样的问题。谢谢。 - Romain Verdier
我在ApplicationStartup事件的开头添加了这一行代码:MinimumSplashScreenDisplayTime = 3000,到目前为止,我还没有遇到过这个异常。但由于这个异常是完全随机的,我不能确定它是否有所帮助。 - Jürgen Steinblock
我找到的唯一“确定”的解决方法是:1. 在项目设置中手动清除应用程序启动画面 2. 在MyApplication_Startup中.Show()它 3. 当启动逻辑完成时关闭它。 - smirkingman
Pavlinll的解决方案运行得非常好。自从我实施了他的代码以来,没有发现任何异常情况。 - Jürgen Steinblock
4个回答

5
将以下代码放入您的闪屏中。如果有一些组件,在Designer.vb文件中可能已经声明了此子程序。只需将其内容移动到您的源代码中并插入第一行即可。
```html

将以下代码放入您的闪屏中。如果有一些组件,在Designer.vb文件中可能已经声明了此子程序。只需将其内容移动到您的源代码中并插入第一行即可。

```
Protected Overrides Sub Dispose(ByVal disposing As Boolean)
    If disposing Then My.Application.SplashScreen = Nothing
    MyBase.Dispose(disposing)
End Sub

我已经进行了深入的分析,包括反编译框架程序集,这应该可以解决问题。详细的解释会更复杂。
我无法在自己的电脑上重现此问题,但错误出现在各种客户机器上。即使有恶意调用,也似乎无法重现它。没有硬件或软件相关的条件来模拟这个问题,但通常出现在慢速或高CPU使用的PC上,当事件循环消息被延迟和工作线程在错误的时刻切换时。

谢谢,我会尝试一下。由于我为此进行了一些聪明的错误处理,客户永远不会注意到这个异常。但是在我的开发机器上,这种情况每周或两周会发生一次(即使在调试时也是如此)。 - Jürgen Steinblock
更新:自从我更新了我的代码库,这个错误再也没有发生过(至少在我的开发机上调试期间)。目前看起来很不错。 - Jürgen Steinblock
不幸的是,这种异常仍然存在很小的可能性。在HideSplashScreen中有对Dispose方法的Invoke调用,这通常是不好的做法。因为当排队的调用被过快地触发时,控件会被处理,而随后的WaitForWaitHandle可能会在这种情况下抛出异常。这无法从代码中修复,并且可能会在单核CPU上发生,当线程在不适当的时刻切换时。 - PavlinII
我打算在 MyBase.Dispose(disposing) 之前放置 Threading.Thread.Sleep(200)。那应该提供足够的时间让 WaitForWaitHandle 开始等待。这是一个丑陋的解决方法。好的解决方案是尽快重置 SplashScreen 并手动释放表单。请投票:https://connect.microsoft.com/VisualStudio/feedback/details/769497/hidesplashscreen-causing-invalidoperationexception - PavlinII
我已经投票了。你应该在你的Microsoft Connect帖子中链接这个帖子。 - Jürgen Steinblock

1
  1. 您能否重现这个问题(即区分某些场景下问题是否出现)?
  2. MyApplication class中正在处理哪些事件?
  3. 尝试在该类中添加以下处理程序,也许异常将在这里被捕获,并提供更多关于堆栈的信息
  4. 当您到达处理程序时,请递归检查是否存在InnerExceptions,并确定您所看到的错误确实是源错误,还是只是重新抛出。
  5. 禁用“仅我的代码”(详见this),您可能能够跟踪问题的来源。

希望对您有所帮助


Private Sub MyApplication_UnhandledException(
    ByVal sender As Object, 
    ByVal e As ApplicationServices.UnhandledExceptionEventArgs) _
       Handles Me.UnhandledException
    Stop
End Sub

对于建议4,非常有趣,谢谢。我已经处理了UnhandledException以显示自定义错误屏幕。另一个事件只有启动事件。这个错误无法重现,因为它只在客户端计算机上完全随机地发生了几次。也许我应该先发布一个调试版本,并希望堆栈跟踪更详细。 - Jürgen Steinblock
在你的电脑上也发生了吗?也许你可以根据这些独特客户端的操作系统(也许全部都是x64 /服务器/其他)来确定,或者它是否会发生在所有计算机上? - Shimmy Weitzhandler
在自定义错误中,尝试查找主要错误中是否存在“InnerException”。 - Shimmy Weitzhandler
不是这样的。我的UnhandledException处理程序循环遍历每个内部异常并记录它能找到的所有内容,没有任何异常。我有一个理论:我的初始化代码通常需要2或3秒钟。我想,在启动事件完成后,启动屏幕会自动关闭。也许在快速的机器上,初始化是在启动屏幕创建之后但在显示之前完成的。现在框架调用HideSplashScreen()并遇到了异常。也许如果初始化太快,我应该尝试添加Thread.Sleep(1000)。 - Jürgen Steinblock
你也可以尝试在启动画面中调用Thread.Sleep,或者尝试从启动画面显式地抛出异常。由于我们无法重现问题,也没有在有问题的机器上进行调试工具,所以很难确定。我感同身受。 - Shimmy Weitzhandler

1
这是我的解决方案。我最终采取的措施是在未处理异常事件中捕获任何InvalidOperationException,直到闪屏消失。
为此,我在MyApplication类中添加了一个名为MainFormLoadingComplete的属性,在我的主窗体的Shown事件中将其设置为true(闪屏一直保持到Form_Load事件被处理)。
我还发现必须在OnInitialize()之前设置MinimumSplashScreenDisplayTime才会生效。希望这有助于避免第一次出现异常。但由于该异常是完全随机的,因此我...
闪屏代码:
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 代码:

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

我的应用程序代码:

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

-1

刚刚看到这个,我们使用相同的技术遇到了完全相同的问题。

我将尝试您的解决方案,但我想让您知道如何重现它:

我们的支持人员花了很多时间研究,最终发现它只会在Windows 7中发生,并且仅在从启动后不久发生。

如果您重新启动Windows,然后立即启动使用此闪屏技术的应用程序,几乎总是会出现错误。

如果您重新启动Windows,等待几分钟,然后启动应用程序,您将永远不会看到错误。

我通过在主窗体的加载事件底部放置sleep(400)来解决它,但有些人仍然看到错误,所以我打算尝试您的解决方案。


我还发现这个异常似乎与窗体加载事件有关(即使堆栈跟踪中没有告诉你),因为并非所有的加载事件代码都会执行(在我的情况下,我加载了一个带有登录框的用户控件,在异常发生后从未显示过,即使使用了我的解决方法)。所以我将大部分代码移动到“Shown”事件中。 - Jürgen Steinblock

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接