Multi-Threaded WinForms Application – MVP

This is the third part of the Multi-Threaded WinForm Applications series, we will discuss how to use Model-View-Presenter (MVP) to inject threads into a WinForm application. In part one of this series, we discussed the basics of Multi-Threaded WinForms. In part two, we discussed using a BackgroundWorker to keep the WinForm application responsive during long running operations. In part four, we will use dynamic proxies to keep our code DRY.In order to use MVP, we must first convert our application to use the MVP pattern.Model (Same as in the other articles)

public class Model{    private const int MILLIS_IN_A_SECOND = 1000;    public string LongRunningMethod() {        Thread.Sleep(8 * MILLIS_IN_A_SECOND);        return "I am done";    }}

View (Interface and Concrete Class)Martin Fowler describes this type of view as the Passive View.The view is responsible for notifying the presenter when the Perform Operation button is pressed (Presenter.GetResults) and setting the results when asked to SetResults.

public interface IView{    IPresenter Presenter { get; set; }    void SetResults(string results);}public partial class MainForm : Form, IView{    private IPresenter presenter;    public IPresenter Presenter    {        get { return presenter; }        set { presenter = value; }    }    public void SetResults(string results)    {        label.Text = results;    }    void Button1Click(object sender, EventArgs e)    {        Presenter.GetResults();    }}

Presenter (Interface and Concrete Class)The presenter is responsible for interacting with the model to GetResults for the View.

public interface IPresenter{    IView View { get; set; }    void GetResults();}public class Presenter : IPresenter{    private IView view;    public IView View    {        get { return view; }        set { view = value; }    }    public void GetResults()    {        View.SetResults(new Model().LongRunningMethod());    }}

Where are the Threads?This is a very good question. Neither the View or the Presenter knows anything about Threading. This is a very good thing. I like to separate the responsibility of threads from the rest of the system. This makes the code easier to read and understand. In order to introduce threads into the system, we need to use the Proxy Pattern.ThreadSafeViewThe ThreadSafeView will wrap an IView and make it thread safe. In order to make the view thread safe, we will need a way to synchronize all access to the view. In WinForms, only the thread that created the GUI may change the state of the GUI. If more than one thread modifies the GUI then strange errors can occur. Luckily, .NET WinForms has a way to make sure all GUI changes are made on the GUI thread; ISynchronizeInvoke.The BeginInvoke method of the ISynchronizeInvoke interface ensures that all requests are executed on the GUI thread. Look at the SetResults(string results) for an example of how to use the ISynchronizeInvoke interface. This approach can get tedious as it requires you to create a delegate for each method of the View’s interface.

Note: If you want to use a more dynamic approach to creating the proxies, then check out the DyanmicProxy from the Castle Project.

public class ThreadSafeView : IView{    private IView adaptee;    private ISynchronizeInvoke synchronizer;    public ThreadSafeView(IView adaptee, ISynchronizeInvoke synchronizer)    {        this.adaptee = adaptee;        this.synchronizer = synchronizer;    }    public IPresenter Presenter    {        get { return adaptee.Presenter; }        set { adaptee.Presenter = value; }    }    private delegate void SetResultsDelegate(string results);    public void SetResults(string results)    {        synchronizer.BeginInvoke(new SetResultsDelegate(adaptee.SetResults), new object[] {results});    }}

ThreadedPresenterThe ThreadedPresenter wraps an IPresenter and invokes each method on the interface in another thread. This is illustrated below, in the GetResults method.

Note: The DynamicProxy would come in handy here as well.Note: I did not use the threadpool in the example below to make the code more readable, but in a production system the threadpool should be applied.Note: Read the next article in the series for more information.

public class ThreadedPresenter : IPresenter{    private IPresenter adaptee;    public ThreadedPresenter(IPresenter adaptee)    {        this.adaptee = adaptee;    }    public IView View    {        get { return adaptee.View; }        set { adaptee.View = value; }    }    public void GetResults()    {        new Thread(delegate() {adaptee.GetResults();}).Start();    }}

Putting it all togetherThe last thing we need to do is wire all the parts together. The below code was taken from the main method of the application.

[STAThread]public static void Main(string[] args){    MainForm form = new MainForm();    Presenter presenter = new Presenter();    presenter.View = new ThreadSafeView(form, form);    form.Presenter = new ThreadedPresenter(presenter);    Application.Run(form);}

ConclusionUsing the MVP pattern allows us to separate the normal application logic from the threading logic. This makes the application more maintainable.If we want to turn off threading, we just need to change the wiring defined in the main method. This has come in handy in more than one debugging session. It is much easier to debug a problem if we are able to turn off threads while debugging. If the application implements threading using the BackgroundWorker, it is much harder to turn off threads.MVP does introduce more complexity (as there are more classes), but I think it is worth it for all but the simplest cases.All code for all the articles can be found in my svn repository. All comments are encouraged and welcome.


2 Responses

  1. […] Multi-Threaded WinForm Application – BackgroundWorker Welcome back to the second installment of a three part series (part 1, part 3). […]

  2. […] This will allow us to revisit this application through out the series (Part 2 – BackgroundWorker, Part 3 – MVP) discussing different ways to inject threads into your WinForms […]

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: