【问题标题】:What happens between Application.Run and Form.Load?Application.Run 和 Form.Load 之间会发生什么?
【发布时间】:2015-02-13 23:19:29
【问题描述】:

我有一个用 VB.NET 为 Framework 4.5 编写的 WinForms 应用程序。 我注意到应用程序的启动时间异常长(我编写的其他在启动时做更多工作的应用程序几乎立即启动,这个应用程序需要> 5秒)多次启动后启动时间不会改变,所以我猜这不是应用程序首次启动期间未缓存的 CLR 代码的情况。

我通过写下启动期间的时间进行了一些测试:

Module modMain
    Public MyLog As System.Text.StringBuilder

    <STAThread>
    Public Sub Main()
        MyLog = New System.Text.StringBuilder

        Application.EnableVisualStyles()
        Application.SetCompatibleTextRenderingDefault(False)
        MyLog.AppendLine("Before run: " & Date.Now.ToString & "," & Date.Now.Millisecond.ToString)
        Application.Run(frmMain)
    End Sub
End Module

Sub Main() 是应用程序入口点。它运行frmMain,我可以控制的第一个真实的东西是Sub InitializeComponent(),由设计师生成:

<System.Diagnostics.DebuggerStepThrough()> _
Private Sub InitializeComponent()
    MyLog.AppendLine("Init Start: " & Date.Now.ToString & "," & Date.Now.Millisecond.ToString)
    'All the control initializations
    MyLog.AppendLine("Init End: " & Date.Now.ToString & "," & Date.Now.Millisecond.ToString)
End Sub

最后我到达Form.Load事件

Private Sub frmMain_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    MyLog.AppendLine("Form_Load Start: " & Date.Now.ToString & "," & Date.Now.Millisecond.ToString)
    '...
    MyLog.AppendLine("Form_Load End: " & Date.Now.ToString & "," & Date.Now.Millisecond.ToString)
End Sub

现在,MyLog 的输出如下:

Before run: 15.12.2014 19:56:47,579
Init Start: 15.12.2014 19:56:51,451
Init End: 15.12.2014 19:56:51,521
Form_Load Start: 15.12.2014 19:56:51,544
Form_Load End: 15.12.2014 19:56:51,547

您可以看到,主要停顿发生在Application.Run()Sub InitializeComponent() 之间。我从其他问题中知道,GUI 线程启动了一个消息循环,但我不知道为什么这个应用程序比其他应用程序慢得多。

所以我的问题是:Application.Run 和我重新获得对代码的控制之间究竟发生了什么,我可以做些什么来加快它的速度吗?那里所做的工作是否与表单上的组件相关?

我已经尝试使用frmMain.ShowDialog() 而不是Application.Run(frmMain),但这导致了相同的结果。我正在使用 Visual Studio Express,所以很遗憾我无法使用更深入的性能分析器。

将其标记为 C# 和 VB.NET,因为两种语言的答案都非常受欢迎。

编辑
我做了更多的测试,包括 SLaks answer 中提出的解决方案。使用NGEN 预编译程序集似乎没有任何明显的效果。所以我猜这不是InitializeComponent代码的JIT编译。

然而,我注意到,在其他系统上,该程序几乎可以立即启动(快 10 倍以上),即使所讨论的计算机在所有方面都较慢。 计算机之间的区别在于操作系统:

Windows 7: Slow start
Windows 8.1: Fast start
Windows Server 2008: Fast start

这些只是更多线索,不知道对解答有没有帮助。

编辑 2
在启动期间查看 ProcMon,我发现执行挂在以下几行:

"15:56:29.3547260","Electrochemical Calculator.exe","5972","CreateFile","C:\Users\Jens\Desktop\Electrochemical Calculator Barebone\Electrochemical Calculator\bin\Release\de\Electrochemical Calculator.resources.dll","PATH NOT FOUND","Desired Access: Read Attributes, Disposition: Open, Options: Open Reparse Point, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a"
"15:56:29.3548019","Electrochemical Calculator.exe","5972","CreateFile","C:\Users\Jens\Desktop\Electrochemical Calculator Barebone\Electrochemical Calculator\bin\Release\de\Electrochemical Calculator.resources\Electrochemical Calculator.resources.dll","PATH NOT FOUND","Desired Access: Read Attributes, Disposition: Open, Options: Open Reparse Point, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a"
"15:56:29.3548612","Electrochemical Calculator.exe","5972","CreateFile","C:\Users\Jens\Desktop\Electrochemical Calculator Barebone\Electrochemical Calculator\bin\Release\de\Electrochemical Calculator.resources.exe","PATH NOT FOUND","Desired Access: Read Attributes, Disposition: Open, Options: Open Reparse Point, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a"
"15:56:29.3549519","Electrochemical Calculator.exe","5972","CreateFile","C:\Users\Jens\Desktop\Electrochemical Calculator Barebone\Electrochemical Calculator\bin\Release\de\Electrochemical Calculator.resources\Electrochemical Calculator.resources.exe","PATH NOT FOUND","Desired Access: Read Attributes, Disposition: Open, Options: Open Reparse Point, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a"
"15:56:32.8796760","Electrochemical Calculator.exe","5972","CreateFile","C:\Windows\Fonts\StaticCache.dat","SUCCESS","Desired Access: Generic Read, Disposition: Open, Options: Synchronous IO Non-Alert, Non-Directory File, Attributes: n/a, ShareMode: Read, Delete, AllocationSize: n/a, OpenResult: Opened"
"15:56:32.8797088","Electrochemical Calculator.exe","5972","QueryStandardInformationFile","C:\Windows\Fonts\StaticCache.dat","SUCCESS","AllocationSize: 9,633,792, EndOfFile: 9,633,792, NumberOfLinks: 1, DeletePending: False, Directory: False"
"15:56:32.8797218","Electrochemical Calculator.exe","5972","ReadFile","C:\Windows\Fonts\StaticCache.dat","SUCCESS","Offset: 0, Length: 60, Priority: Normal"
"15:56:32.8797429","Electrochemical Calculator.exe","5972","CreateFileMapping","C:\Windows\Fonts\StaticCache.dat","FILE LOCKED WITH ONLY READERS","SyncType: SyncTypeCreateSection, PageProtection: "

该问题仅在 Release 版本中进一步出现,并且仅在我直接从 Windows 资源管理器启动程序时出现。调试版本会立即启动(0.3 秒,而 5-10 秒),从 Visual Studio 启动发布版本也是如此。

【问题讨论】:

  • Sub New 中添加一个日志调用,看看它落在哪里。
  • 您可以尝试运行Process Monitor 以查看在这五秒钟内较低级别发生了什么。
  • 发生了很多事情。如果你真的想知道发生了什么,可以看看这本书“Windows 内部”amazon.com/Windows-Internals-Edition-Developer-Reference/dp/…
  • 查看您的编辑 2,很明显在访问资源时存在一些问题(因为它们不在正确的路径上,或者因为用户被拒绝访问它们),这可能是导致通常会导致此类延迟的异常(可能被捕获)。检查所有文件是否正常,并且运行它们的用户对您运行它们的文件夹具有权限

标签: c# vb.net winforms app-startup


【解决方案1】:

就像汉斯说的,你已经消除了明显的问题,对我来说,问题似乎是环境问题......那么在极端条件下你如何解决这个问题......

由于该过程始终需要 5 秒或更多秒(从资源管理器启动时),以下是在这种情况下我会考虑采取的一些方法 -

注意 - 由于可能存在时间问题,您可能需要重复实验几次才能获得不错的结果。显然,我仍然很难保证结果......如果我亲自做,如果这些高水平的想法不能 100% 奏效,我希望即兴发挥。但我肯定会给他们一个机会。

  • 使用Procdump 启动进程,并在进程的Elapsed Time 性能计数器达到某个值(如2、3 或4)后进行n full 转储.然后,您可以在 windbg(大多数信息,难以使用)或 VS.Net(更易于使用,但可能会或可能不会显示您想要查找的内容,即使它存在 - 请使用混合模式调试时打开这些转储打开转储)。

  • 在类似的行上,但功能较弱,更难正确。配置ProcessExplorer 以在查看进程属性时显示本机堆栈跟踪...一旦启动进程,切换到ProcessExplorer,并查看进程中各种线程的堆栈跟踪。这更棘手,因为根据线程的数量,手动查看它们的堆栈跟踪可能根本无法正常工作。可能值得一试或二,如果它确实以最少的努力使问题变得明显。

在任何一种情况下,请务必将您的符号配置到 Microsoft 的公共符号服务器。这样您就可以从本机堆栈跟踪中获取大部分信息。

总而言之,这个想法是......在这种情况下,如果下面的事情需要几秒钟才能完成,则某些高级函数应该很有可能显示在堆栈跟踪中。

注意 - 一旦你知道了高级功能,它可能是故事的结局,或者可能仍然需要将它与其他系统级进程相关联..(再次反- 病毒是典型的例子)。但你当然可以期待有更多的线索来帮助支持任何这样的假设。

【讨论】:

    【解决方案2】:

    嗯,您消除了所有导致启动延迟的正常来源。它绝对与 Application.Run() 没有任何关系,直到 表单的 InitializeComponent() 方法完成运行后才会开始。并且您通过使用 Ngen.exe 消除了抖动开销。一定要区分冷启动延迟和热启动延迟,如果只是第一次启动程序时很慢那么这是硬件问题,你需要一个更快的磁盘。仅在 一些 机器上运行缓慢这一事实强烈表明存在环境问题。

    由在 InitializeComponent() 中运行的代码触发。换句话说,表单上控件的所有构造函数和属性设置器。 通常只需要很少的时间,但周围肯定有麻烦制造者。您需要寻找一个重要的控件,尤其是在底层使用 COM(又名 ActiveX)的那种。像浏览器一样。还有更多的可能性,任何类名以“Ax”开头的东西。此类控件可以加载很多其他 DLL,并且很容易受到安全软件的关注。

    一些调试技巧:

    • 确保您有一个良好的备份并开始从表单中删除控件,从不平凡的开始。
    • 单步执行 InitializeComponent() 方法时,5 秒的延迟足以引起注意。立即告诉您哪个特定控件和语句导致延迟。
    • 将调试器切换到非托管模式可以告诉您更多关于将哪些其他 DLL 加载到您的程序中的信息。项目+属性,调试选项卡,勾选“启用本机代码调试”选项。调试时请留意输出窗口,您将看到加载任何非托管 DLL 的加载通知。这可以查明导致延迟的特定 DLL。
    • 如果可能,请禁用反恶意软件,这样您就可以消除由于不适当的 DLL 扫描造成的延迟。
    • SysInternals' TcpView 实用程序非常适合检测网络延迟。当您看到您的程序联系 CRL 服务器时要小心,证书吊销列表查询可能会很慢。
    • SysInternals' Process Monitor 实用程序非常适合查看由于大多数其他原因导致的延迟。跟踪可以非常很大,如果您需要另一双眼睛,请保存并将其发布到文件共享网站上。

    【讨论】:

    • 感谢您的精彩回答。我现在首先注意到该问题仅在“发布”构建模式下发生,并且仅在从资源管理器启动时(从 VS 开始也很快)。当我查看 ProcMon 时,它似乎挂在与某些资源文件的创建相关的特定行(我会将这些行添加到我的问题中)。
    • 哦,但无论如何,这与表单的复杂性有关。当我删除控件时,它会加快速度。所以设计师在某种程度上可能是罪魁祸首。但是,我无法确定两个版本之间生成的代码(查看 ILSpy)的显着差异。然而,优化步骤肯定会导致差异(我在发布版本中关闭了它,它又很快了)。
    • 强烈暗示涉及反恶意软件。
    • 由于赏金已用完,我将奖励此答案,因为它提供了最详细的外观。我还无法追踪问题的根源,但我会继续测试。反恶意软件仍然可能是罪魁祸首,即使我试图避免它,但我可以进一步测试。再次感谢您的贡献!
    【解决方案3】:

    这段时间用于加载表单控件使用的每个程序集,并 JIT 处理 InitializeComponent 方法。

    【讨论】:

    • 我明白了,这是有道理的,因为我在表单中有相当多的控件,放置在各种 TableLayoutPanel 中,因此 InitializeComponent 的代码相当长。另一方面,我只引用了 7 个 .NET 程序集,仅此而已,所以我猜 JIT 编译是罪魁祸首。有什么方法可以加快速度,还是我只需要添加一个启动画面并使用它?
    • 你可以生成程序集。
    • 我现在已经尝试过了,但没有明显的效果。如果您愿意,请查看已编辑的问题。
    猜你喜欢
    • 2012-10-11
    • 2022-01-16
    • 2011-08-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-24
    • 1970-01-01
    • 2021-09-20
    相关资源
    最近更新 更多