views:

471

answers:

5

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 application, 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.

+1  A: 

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)

SLaks
That's an interesting approach. Can you expand on how to actually make an STA Thread.
AngryHacker
Spawning a thread is counter-productive. The goal is to minimize the time it takes for the splash screen to show, so why slow it down by sharing its cycles with the rest of the app load process? Best to devote everything to showing the splash, then load the rest of the app.
Charles
@Charles: If you show the splash on the main thread, it won't get any messages and will stop responding.
SLaks
@Slaks: true, but not usually a problem in my experience (see comment on my answer). Ultimately it's a design decision, and I think both approaches have their place. (By the way, the splash screen *will* get messages once the app loads, though I realize this is likely moot.)
Charles
+1  A: 

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).

andypaxo
+1  A: 

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.

Charles
If you show the splash on the main thread, it won't be able to process Windows messages and will stop responding.
SLaks
Yes, and that may be an issue if the app takes a very long time to load, and the developer wants the user to be able to interact with the splash screen while waiting. But for apps that load in a reasonably short time, interaction with the splash screen isn't normally desired.
Charles
If it loads in a short time there's little point in using a splash screen.
Hans Passant
But splash screens are... splashy. :)
Charles
+5  A: 

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.

Hans Passant
+1  A: 

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.

Digicoder