如何加速加载启动画面

7
我正在优化一个WinForms应用程序的启动。我发现其中一个问题是闪屏窗体的加载。它需要大约半秒到一秒的时间。
我知道在UI部件上使用多线程是不可取的,但考虑到闪屏界面是应用程序中相当独立的一部分,是否可以通过将其放在其他线程中(可能类似于Chrome的方式),以此来缓解它对性能的影响,这样重要的应用程序部分就可以开始运行了。
6个回答

9

.NET框架已经非常好地支持Windows Forms应用程序中的启动画面。请查看这个线程以获取代码示例。它确实针对热启动时间进行了优化,确保在初始化主应用程序之前启动画面和线程。


4
如果你的目标是尽快显示闪屏界面,那么创建一个线程没有任何好处。有几种方法可以实现闪屏界面,其中一种更复杂的方法在这里提到:https://dev59.com/6HRC5IYBdhLWcg3wKtr2,但是我用过一种简单的方法,完全成功:

确保首先加载并显示闪屏窗体,然后在用户查看漂亮的闪屏界面时继续加载你的应用程序。当主窗体加载完成时,在显示自己之前关闭闪屏窗体(一种简单的方法是将闪屏窗体传递给主窗体的构造函数):

static void Main()
{
    Application.SetCompatibleTextRenderingDefault(false);
    SplashForm splash = new SplashForm();
    splash.Show();
    splash.Refresh(); // make sure the splash draws itself properly
    Application.EnableVisualStyles();
    Application.Run(new MainForm(splash));
}

public partial class MainForm : Form
{
    SplashForm _splash;
    public MainForm(SplashForm splash)
    {
        _splash = splash;
        InitializeComponent();
    }

    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);

        // or do all expensive loading here (or in the constructor if you prefer)

        _splash.Close();
    }
}

替代方案:如果您不想将闪屏传递给主窗体(可能看起来不太优雅),那么可以订阅主窗体的加载事件,在那里关闭闪屏:

static class Program
{
    static SplashForm _splash;

    [STAThread]
    static void Main()
    {
        Application.SetCompatibleTextRenderingDefault(false);
        _splash = new SplashForm();
        _splash.Show();
        _splash.Refresh();
        Application.EnableVisualStyles();
        MainForm mainForm = new MainForm();
        mainForm.Load += new EventHandler(mainForm_Load);
        Application.Run(mainForm);
    }

    static void mainForm_Load(object sender, EventArgs e)
    {
        _splash.Dispose();
    }
}

此线程所述,这种解决方案的潜在缺点是用户无法与启动画面进行交互。然而,通常并不需要这样做。


如果您在主线程上显示启动画面,它将无法处理Windows消息并停止响应。 - SLaks
是的,如果应用程序加载时间非常长,并且开发人员希望用户在等待时能够与启动画面进行交互,那么这可能会成为一个问题。但对于加载时间合理的应用程序来说,通常不需要与启动画面进行交互。 - Igby Largeman
1
如果应用程序加载速度很快,那么使用启动画面就没有太大意义了。 - Hans Passant
但启动画面很...引人注目。 :) - Igby Largeman

1

是的。

你需要创建一个新的STA线程,使用Application.Run来显示闪屏界面,然后在主线程上主窗体就绪后,使用Invoke调用Close

编辑: 例如:

static SplashForm splash;

Thread splashThread = new Thread(delegate() {
    splash = new SplashForm();
    Application.Run(splash);        //Blocking call on separate thread    
});
splashThread.SetApartmentState(ApartmentState.STA)
splashThread.Start();

LoadApp();

//In MainForm_Shown:
splash.BeginInvoke(new Action(splash.Close));

为了获得最佳性能,您应该使您的Main方法显示启动画面,然后调用一个单独的方法来加载应用程序。这样,在显示启动画面之后,所有程序集都将被加载。(当您调用一个方法时,JITter会在方法开始执行之前加载它使用的所有类型)

这是一个有趣的方法。你能详细说明如何实际创建STA线程吗? - AngryHacker
生成线程是适得其反的。目标是尽量缩短启动画面显示所需的时间,因此与应用程序加载过程共享其周期会使其变慢。最好将所有资源都用于显示启动画面,然后再加载应用程序的其余部分。 - Igby Largeman
@Charles:如果你在主线程上显示闪屏,它将不会收到任何消息并停止响应。 - SLaks
通常情况下,这不是问题(请参见我的答案评论)。最终这是一个设计决策,我认为两种方法都有其适用的场合。(顺便说一句,启动画面在应用程序加载后确实会收到消息,尽管我意识到这可能无关紧要。) - Igby Largeman

1

只要所有的UI都留在一个线程上,WinForms中的多线程就可以了。

这就是闪屏通常的做法。重要的工作在后台线程上完成,而闪屏窗口在UI线程上显示,让用户知道程序的其余部分很快就会出现。

在重要的事情发生后,引发一个事件,让UI线程知道是时候隐藏闪屏了(只需记住使用Invoke()将事件处理程序马歇尔回到UI线程以关闭闪屏即可)。


1

答案实际上与感知有关。有各种方法,如NGEN汇编、将事物放入GAC,但您应该了解的是实际发生了什么。

C#需要时间来加载虚拟机、加载引用的程序集以及根据启动画面上的内容加载程序集。然后,从“冷”启动到启动第一个屏幕还需要一段时间。

这是因为当您首次访问屏幕时正在进行JIT编译。

我使用的策略是传统的轻量级启动页面,它可以快速加载以便可见,但在后台我生成一个线程并加载一个不可见的表单。此表单具有我打算使用的所有控件,因此JIT编译器正在执行其任务并加载程序集。这提供了手法的错觉,使其具有响应性。从启动+可见启动页面+暂停+单击第一个选项所需的时间大于线程执行、清理和卸载表单所需的时间。

否则,应用程序在首次启动时对用户来说会显得笨重和缓慢。屏幕的热启动要快得多,因为程序集和JIT已经完成。


1
我们通过提供一个微小的本地C++应用程序作为启动屏幕来实现它。
然后按照以下过程进行:
1. 用户双击应用程序图标。 2. C++应用程序启动,显示一个闪屏位图作为WS_TOPMOST。 3. C++应用程序启动主C#应用程序。 4. C#应用程序启动,最终通过一个简单的文件通知C++应用程序退出。 5. C++应用程序退出。
C++应用程序还具有超时功能(以防C#应用程序崩溃),如果在30秒内未收到通知退出,则会自动退出。
这种方法的缺点在于您无法将应用程序固定到任务栏。如果您将C++应用程序(即最终用户的主要应用程序)固定到任务栏上,您将获得另一个任务栏上的任务,因为C#应用程序是不同的。我认为,我可以通过在C++和C#应用程序的应用程序清单中提供设置来解决这个问题,以指示它们在任务栏方面是“相同”的应用程序。

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