How to speed up loading the splash screen
I am optimizing the startup of a WinForms app. One issue I identified is the loading of the splash screen form. It takes about half a second to a second.
I know that multi-threading is a no-no on UI pieces, however, seeing how the splash screen is a fairly autonomous piece of the a开发者_JAVA技巧pplication, is it possible to somehow mitigate its performance hit by throwing it one some other thread (perhaps in the way Chrome does it), so that the important pieces of the application can actually get going.
The .NET framework already has very good support for splash screens in Windows Forms apps. Check this thread for a code sample. It is indeed optimized for warm startup time, it makes sure the splash thread and screen is up and running before initializing the main app.
There's nothing to be gained from spawning a thread if your goal is to get the splash screen up as quickly as possible.
There are several ways to do splash screens, and a more sophisticated one is mentioned here, but this is an easy method I have used with complete success:
Just ensure you load and show the splash form first, and then continue to load your app while the user is looking at the pretty splash screen. When the mainform is done loading, it can close the splash right before it shows itself (a simple way to do this is pass the splash form to the mainform in its constructor):
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();
}
}
Alternative: If you prefer not to pass the splash to the MainForm (maybe it seems inelegant), then subscribe to the MainForm's Load event, and close the splash screen there:
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();
}
}
As mentioned in this thread, the potential downside to this solution is that the user won't be able to interact with the splash screen. However, that usually isn't required.
Multithreading in WinForms is okay as long as all the UI stays on one thread.
This is just how splash screens are usually done. The important work is done on a background thread, while the splash screen window is shown on the UI thread to let the user know that the rest of the program will appear soon.
After the important stuff has happened, raise an event to let the UI thread know that it is time to hide the splash screen (just remember to marshal the event handler, using Invoke(), back onto the UI thread in order to close the splash screen).
Yes.
You need to make a new STA thread that shows the splash screen using Application.Run
, then call Close
using Invoke
after the main form is ready (on the main thread).
EDIT: For example:
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));
For optimal performance, you should make your Main
method show the splash screen, then call a separate method that loads the application. This way, all of the assemblies will be loaded after the splash screen is shown. (When you call a method, the JITter will load all of the types that it uses before the method starts executing)
The answer is really about perception. There are various methods, NGEN an assembly, putting things in the GAC, but what you should understand is what is really going on.
C# require time to load the virtual machine, load referenced assemblies, and load assemblies based upon what is on that splash screen. And then it still takes awhile from a "cold" start to launch the first screen.
This is because of the JIT compiling that is going on when you first access a screen.
A strategy that I use is a traditional lightweight splash page that loads quickly so its visible, but in the background I spawn a thread and load an invisible form. This form has all the controls that I intend to use and so the JIT compiler is doing its thing and its loading the assemblies. This provides the illusion of responsiveness with slight of hand. The time it takes from launch + visible splash page + pause + time to click 1st option is greater then the time it takes for the thread to execute and then cleanup and unload the form.
Otherwise, applications appear to be clunky and slow for users when it first starts. Warm startup of screens is much faster because the assemblies and JIT has finished.
We do it by providing a tiny small native C++ application that serves as the splash screen.
It then follows this process:
- User double clicks the application icon.
- The C++ app starts, showing a splash bitmap as WS_TOPMOST.
- The C++ app launches the main C# app.
- The C# app starts, finally notifying the C++ app (through a simple file) to quit.
- The C++ app quits.
The C++ application also has a timeout (in case the C# app crashes) and quits automatically if it does not get notified to quit within 30 seconds.
This approach has its drawbacks in the fact that you cannot pin the application to the task bar. If you pin the C++ app (which is the main app for the end user), you get another task on the task bar, because the C# app is different. I think, I could solve this by providing settings in the application manifest of both the C++ and C# application to instruct them to be the "same" application in terms of the task bar.
精彩评论