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.
- Have the main form inherit from
Microsoft.VisualBasic.WindowsFormsApplicationBase
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
Marshal C++ Struct Array into C#
Bundler Not Including .Min Files
How to Write an Async Method with Out Parameter
Is There a Built-In Method to Compare Collections
Deserializing Dates with Dd/Mm/Yyyy Format Using JSON.Net
Using Side-By-Side Assemblies to Load the X64 or X32 Version of a Dll
Swap Two Variables Without Using a Temporary Variable
Do C# Timers Elapse on a Separate Thread
Regex Match Multiple Times in String
What Is the Minimum Client Footprint Required to Connect C# to an Oracle Database
Calling JavaScript Function from Codebehind
Replace Multiple Characters in a C# String
How to Instantiate a Class Given Its String Name
Read from Location on Console C#
Return View as String in .Net Core
Select Multiple Items from a Datagrid in an Mvvm Wpf Project