I answered a question at stackoverflow.com the yesterday about how to suppress a ThreadAbortException. The case was that some code was displaying a splash form during application startup. The form was created and displayed on a background thread so that it would not interfere with the main thread while the application was loaded. I provided an answer that had a slightly different approach than that of the original poster, and that caused the background thread to exit in a more controlled manner. Then I figured that this is a rather common thing to do in an application, so I thought I might try to create a more reusable solution.

The key in the solution is to create and display the splash on a separate thread, and using the Form.ShowDialog to show it. The ShowDialog method will display the form and block the execution of the method that calls ShowDialog until the form is closed. Since this all happens on another thread than the main application thread, the application can continue its work while the splash form shows on the screen.

My goal was to create a SplashUtility class that would require a minimum of extra implementation to use in a project, but that would still not put up any constraints on the splash form, so a base class for splash forms was out of the question. Generics and Interfaces to the rescue! After writing the code and have it undergo a few revisions, I came up with the solution presented here.

First we need to have a defined way of communicating with our splash form. What I needed as a minimum was a way of showing it and closing it in a thread safe manner. The Form class supplies all this through the ShowDialog (and Show), Close and BeginInvoke (and Invoke) methods. So I had my interface declare these methods, using method names and signatures identical to those in the Form class. That way, any Form will implicitly implement these parts of the interface without the need of any additional code.

After that I only needed to complete the interface with any methods that I would need to show information in the splash during application start. In my case I chose to add a SetStatusText method. The finished interface looks like this:

using System;
using System.Windows.Forms;
public interface ISplashForm
{
    /* 
     * The following three methods are implemented by the Form 
     * class (directly or indirectly) already, so when you declare 
     * a form as implementing this interface, BeginInvoke, ShowDialog 
     * and Close are automatically mapped to the already existing 
     * methods in the Form class; you don't need to do anything 
     * for them to work.
     */
    IAsyncResult BeginInvoke(Delegate method);
    DialogResult ShowDialog(IWin32Window owner);
    void Close();


    /* This is the only method you will need to implement in your 
     * form the thread synchronization is taken care of in the 
     * SplashUtility class, so when the call is invoked in your splash 
     * form, it is being executed on the correct thread.
     */
    void SetStatusText(string text);

}

 

The next task was to create the splash form. That was now a very simple task. This is the full code:

using System.Windows.Forms;
public partial class SplashForm : Form, ISplashForm
{
    public SplashForm()
    {
        InitializeComponent();
    }

    // Sets the text of the status text label in a thread-safe manner.
    public void SetStatusText(string text)
    {
        _statusText.Text = text;
    }

}

 

Now I wanted to put together a utility class that would make this extremely simple to use. This is what I came up with. By using generics the utility is not limited to a specific splash form implementation. The only requirements are that that the class implements the ISplashForm interface, and that it has a parameterless constructor.

using System;
using System.Windows.Forms;
using System.Threading;
public static class SplashUtility<T> where T : ISplashForm
{

    /// <summary>
    /// static variable that will hold a reference to the 
    /// splash form instance 
    /// </summary>
    private static T _splash = default(T);

    /// <summary>
    /// Creates an instance of the splash form on a separate thread
    /// and displays it
    /// </summary>
    public static void Show()
    {
        
        // Show the splash form
        ThreadPool.QueueUserWorkItem((WaitCallback)delegate
        {
            _splash = Activator.CreateInstance<T>();

            // by showing the form using ShowDialog this thread will be blocked
            // until the form is closed
            _splash.ShowDialog(null);
        });
    }

    /// <summary>
    /// Closes the current splash. This method is thread safe.
    /// </summary>
    public static void Close()
    {
        if (_splash != null)
        {
            _splash.BeginInvoke((MethodInvoker)delegate { _splash.Close(); });
        }
    }

    /// <summary>
    /// Sets the text to be displayed in the current splash form. This method
    /// is thread safe.
    /// </summary>
    public static void SetStatusText(string text)
    {
        if (_splash != null)
        {
            _splash.BeginInvoke((MethodInvoker)delegate { _splash.SetStatusText(text); });
        }
    }
}

 

That’s it. The only thing remaining is to put it to good use:

// display the splash form
SplashUtility<SplashForm>.Show();

// update the status text of the current splash form
SplashUtility<SplashForm>.SetStatusText("Working really hard...");

// ...and close it
SplashUtility<SplashForm>.Close();

 

I have created a small sample project for demonstration. You can download it here:
Alcedo.ThreadSafeSplash.zip (150 KB)


I am working for a great consultancy company called Diversify, located in Sweden. We are hiring skilled .NET developers. If you are interested, don't hesitate to get in touch with me.

Comments

April 25. 2012 21:57

BSOD

Thoughts on how this code needs to be tweaked to function properly in .NET 4.0? No longer does the main form wait to be displayed until the splash screen is done.

BSOD