6

Given the class below, to launch a splash screen on an alternate thread:

public partial class SplashForm : Form
{
    private static Thread _splashThread;
    private static SplashForm _splashForm;    

    public SplashForm()
    {
        InitializeComponent();
    }

    // Show the Splash Screen (Loading...)      
    public static void ShowSplash()    
    {        
        if (_splashThread == null)        
        {            
            // Show the form in a new thread.          
            _splashThread = new Thread(new ThreadStart(DoShowSplash));            
            _splashThread.IsBackground = true;            
            _splashThread.Start();        
        }    
    }    

    // Called by the thread.  
    private static void DoShowSplash()    
    {        
        if (_splashForm == null)            
            _splashForm = new SplashForm();       

        // Create a new message pump on this thread (started from ShowSplash).       
        Application.Run(_splashForm);
    }    

    // Close the splash (Loading...) screen.
    public static void CloseSplash()    
    {        
        // Need to call on the thread that launched this splash.      
        if (_splashForm.InvokeRequired)            
            _splashForm.Invoke(new MethodInvoker(CloseSplash));        
        else            
            Application.ExitThread();    
    }
}

This is called and closed with the following respective commands

SplashForm.ShowSplash();
SplashForm.CloseSplash();

Fine.

I am not exactly new to the TPL, of course we can show the form on another thread using something as simple as:

Task task = Task.Factory.StartNew(() => 
{
    SomeForm someForm = new SomeForm();
    someForm.ShowDialog();
};

My issue is closing this SomeForm down when you are ready. There must be a better way than creating a public static method in the SomeForm class like

private static SomeForm _someForm;
public static void CloseSomeForm()    
{        
    if (_someForm.InvokeRequired)            
        _someForm.Invoke(new MethodInvoker(CloseSomeForm));        
}

My question is, what is the best way to do the same thing as shown using the SplashForm class above using the Task Parrallel Library (TPL)? Specifically, the best way to close the form invoked on another thread from the UI.

MoonKnight
  • 23,214
  • 40
  • 145
  • 277

2 Answers2

2

Your question does not seem to be so much about a difference between Thread and Task because what you want is to get rid of the "dirty" static state. I suggest you encapsulate it into a class:

class SplashController
{
    public void Run() {
        _someForm = new SomeForm();
        someForm.ShowDialog();
    }

    private SomeForm _someForm;
    public void CloseSomeForm()    
    {        
        if (_someForm.InvokeRequired)            
            _someForm.Invoke(new MethodInvoker(CloseSomeForm));        
    }
}

You can call Run using whatever threading mechanism you like. CloseSomeForm does not use threading so it is independent of this problem.

You can now store a reference to an instance of SplashController wherever you like. In local variables or indeed in a static variable. The latter makes sense because there is exactly one splash screen.

Because the static state is now well encapsulated I don't see any problem with it being statically held.

usr
  • 168,620
  • 35
  • 240
  • 369
  • Great answer, great little idea, not thought of that at all!? My main grip for what I had previously was the use of `InvokeRequired`. I suppose there is not another nicer mechanism for doing what I want. – MoonKnight Mar 08 '13 at 16:51
  • 1
    TPL does have a way to marshal a call onto the UI thread using TaskScheduler.FromCurrentSynchronizationContext. Unfortunately, this method must be called on the UI thread to obtain the scheduler. So in order to use it you have to play a dance to obtain it on the UI thread and move it to your other thread. I wouldn't recommend it. – usr Mar 08 '13 at 17:02
  • I am aware of the `TaskScheduler` route, but it is not nice in this case for the reasons you have stated. In the above I think you wanted `Run(){_someForm = new SomeForm();...}`. – MoonKnight Mar 08 '13 at 17:04
  • 1
    Sure. I was a bit sloppy, just wanted to get the idea across. – usr Mar 08 '13 at 17:43
2

You probably shouldn't do something like this

Task task = Task.Factory.StartNew(() => 
{
    SomeForm someForm = new SomeForm();
    someForm.ShowDialog();
};

because it would require a message loop to be present on the exact thread that creates the Form, which is a ThreadPool thread. But I haven't tested this.

You could try this:

public static Task<SplashForm> ShowSplash()    
{        
    var tcs = new TaskCompletionSource<SplashForm>();

    // Show the form in a new thread.          
    _splashThread = new Thread(() =>
    {
        var splashForm = new SplashForm();       

        tcs.SetResult(_splashForm);

        // Create a new message pump on this thread (started from ShowSplash).       
        Application.Run(splashForm);
    });

    _splashThread.IsBackground = true;            
    _splashThread.Start();        
}    

this would allow you to remove the static modifier from CloseSplash:

// Close the splash (Loading...) screen.
public void CloseSplash()    
{        
    // Need to call on the thread that launched this splash.      
    if (this.InvokeRequired)            
        this.Invoke(new MethodInvoker(CloseSplash));        
    else            
        Application.ExitThread();    
}

May be used like this:

var form = await SplashForm.ShowSplash();
form.CloseSplash();
Daniel C. Weber
  • 1,011
  • 5
  • 12
  • I like this. Thanks for your time. – MoonKnight Mar 08 '13 at 17:02
  • Note that SetResult may execute synchronously and you might get strange behaviour, depending on what you do after the await you might not hit the Application.Run line when you think you should. If in doubt, load off SetResult on a separate Task (with Task.Run). – Daniel C. Weber Mar 08 '13 at 17:07
  • @bluesman You got my attention with your comment. Care to explain a little bit more? – Joel May 03 '13 at 15:54
  • @Joel Check my previous question: http://stackoverflow.com/questions/12693046/configuring-the-continuation-behaviour-of-a-taskcompletionsources-task – Daniel C. Weber May 06 '13 at 06:32