Multi-Threaded Splash Screen in C#

Multi-threaded splash screen in C#?

Well, for a ClickOnce app that I deployed in the past, we used the Microsoft.VisualBasic namespace to handle the splash screen threading. You can reference and use the Microsoft.VisualBasic assembly from C# in .NET 2.0 and it provides a lot of nice services.

  1. Have the main form inherit from Microsoft.VisualBasic.WindowsFormsApplicationBase
  2. Override the "OnCreateSplashScreen" method like so:

    protected override void OnCreateSplashScreen()
    {
    this.SplashScreen = new SplashForm();
    this.SplashScreen.TopMost = true;
    }

Very straightforward, it shows your SplashForm (which you need to create) while loading is going on, then closes it automatically once the main form has completed loading.

This really makes things simple, and the VisualBasic.WindowsFormsApplicationBase is of course well tested by Microsoft and has a lot of functionality that can make your life a lot easier in Winforms, even in an application that is 100% C#.

At the end of the day, it's all IL and bytecode anyway, so why not use it?

c# communication between splashscreen and mainform into different thread

You need to invoke the SplashScreen to change it's value from an other thread like

this.splashy.Invoke((MethodInvoker) delegate { this.splashy.LabelText = "Requested" + repeats + "Times"; });

Note that the constructor

splashy = callingForm as SplashScreen;

allows splashy to be null - this causes your current NullReferenceException.

This problem is locaed in this snipped:

        Thread splashThread = new Thread(new ThreadStart(
delegate
{
splashy = new SplashScreen();
Application.Run(splashy);
}
));
splashThread.SetApartmentState(ApartmentState.STA);
splashThread.Start();
//run form - time taking operation
MainForm mainForm = new MainForm(splashy);

The new thread you are start is not fast enought to create the instance of SplashScreen before you pass it. The result - you are passing null.

Fix it by create the splashy before your thread starts:

        splashy = new SplashScreen();
Thread splashThread = new Thread(new ThreadStart(
delegate
{
Application.Run(splashy);
}

Splash screen being accessed from thread it was not created on?

You need to create the SplashScreen on the same thread where you're using it.

But wait, that's what I'm doing, isn't it? Well, no - you're seeing a quite typical race condition.

The core of your problem, I suspect, is using Lazy to initialize the splash screen, combined with not waiting for the form to be created in your ShowSplashScreen method. In your main form, you refer to SplashScreen.Instance. Now, if the first thread that tried to read the instance is your splashscreen message loop, you're fine - that's the 19 in 20.

However, it's perfectly possible that the main UI thread gets there first - you don't block in ShowSplashScreen. In that case, the splash screen is created on the main UI thread, and you're in trouble - and good thing you're not using InvokeRequired, because that would have hidden the error even further.

Why does this have anything to do with the new login form? Well, I suspect that it's a timing thing, really - your code is broken with or without the login form. However, ShowDialog starts a new message loop, similar to Application.Run. This also means that a synchronization context has to be created - something that would otherwise only happen on your Application.Run(MainForm.Instance) line. The key point is that you've managed to make your race condition much wider - there is no longer as much time between the ShowSplashScreen call and the first time the splash screen is accessed in MainForm - and the result is BOOM.

Do not allow the ShowSplashScreen method to return until the instance is properly created, and you'll be fine. Multi-threading is hard - don't try to guess your way around. A good starting point would be http://www.albahari.com/threading/ - make sure you pay plenty of attention to proper synchronization and signalling.

Creating splashscreen without creating multi-threading form

Try something like this:

public partial class FormTicker : Form
{
Timer timer;
public FormTicker()
{
timer = new Timer();
InitializeComponent();
timer.Interval = 2000;
timer.Tick += new EventHandler(timer_Tick);
timer.Start();
}

void timer_Tick(object sender, EventArgs e)
{
timer.Stop();
FormMain formMain = new FormMain();
formMain.Show();
this.Hide();
}
}

Closing the splash screen from a different thread?

If your splash screen form is named mySplashScreen:

mySplashScreen.Invoke(new MethodInvoker(delegate {
mySplashScreen.Close();
mySplashScreen.Dispose();
}));


Related Topics



Leave a reply



Submit