2
\$\begingroup\$

I'm trying to find a good pattern for new applications (and for refactoring old ones).

Maybe it's just me, but I dislike spawning my main logic thread from inside my main form thread (so far I always have 2+ threads in my apps and beefy hardware, also .NET 4.5). I feel it conflicts with the separation of concerns principle.

So is this a good pattern?

Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.Threading.Tasks;

namespace MyApp
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            try
            {
                // Prevent multiple instances of the application from opening
                using (new SingleGlobalInstance(1000))
                {
                    // Run Main Logic/Sequence Task
                    Task.Factory.StartNew(() =>
                        {
                            new MainTask()
                                .Run();
                        });

                    // Display GUI
                    Application.EnableVisualStyles();
                    Application.SetCompatibleTextRenderingDefault(false);
                    Application.Run(new MainForm());    // Blocks until Form Closes
                }
            }
            catch (TimeoutException)
            {
                MessageBox.Show("An instance of the application is already open.");
            }
            catch (Exception ex)
            {
                // Log Error
                // ... log to file...

                // Display Error to User
                MessageBox.Show(ex.Message);
            }
        }
    }
}

MainTask.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace MyApp
{
    public class MainTask
    {
        // Do Work
        public void Run()
        {
            Console.WriteLine("MainTask - Running");
            Thread.Sleep(10000);
        }

        // Constructor
        public MainTask()
        {
            Console.WriteLine("MainTask - Constructed");
        }

        //// Destructor
        //~MainTask()
        //{
        //    // Destructor defined for purposes of debugging
        //    Console.WriteLine("MainTask - Destructed");
        //}
    }
}

MainForm

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace MyApp
{
    public partial class MainForm : Form
    {
        // Constructor
        public MainForm()
        {
            InitializeComponent();
        }
    }
}
\$\endgroup\$
1
  • 1
    \$\begingroup\$ Are you planning on having the MainTask method actually do any work? If not, why have it? If so, how will it interact with the UI if it has no reference to the UI and the UI has no reference to it? \$\endgroup\$ – Steven Doggart Mar 15 '13 at 13:39
2
\$\begingroup\$

I would second Steven Doggart's comment about it being pointless to spawn a thread that does nothing.

To address your separation of concerns issue, though: If you are using one of a number of design patterns for your UI (e.g., MVC, MVVM, MVP, etc.), your MainForm class will merely be binding to one or more data objects and raising events from the user. The real work and asynchrony will be done in another class.

For example, in the MVP pattern, you might have MainForm, MainPresenter, and MainViewModel.

  • MainForm binds various controls to properties exposed by MainViewModel
  • MainViewModel implements INotifyPropertyChanged and raises PropertyChangedEvents when those properties change.
  • MainForm updates those properties as the result of users manipulating those controls.
  • MainPresenter responds to events raised by MainForm and updates MainViewModel properties as well, causing MainForm to respond through the PropertyChangedEvents

It is then up to MainPresenter (or more appropriately, other classes MainPresenter uses) to figure out how to handle threading. MainForm itself never has to know or care that you are using separate threads.

\$\endgroup\$
1
  • \$\begingroup\$ Yes, I started to work through fleshing out my code above and found it to be a bit tedious. The "thread that doesn't do anything" in my case would monitor PLC inputs/sensors as this is for an industrial machine I'm working on. I did make a failed attempt at understanding MVP about 2 or 3 months ago... and the thing that I didn't really understand was how to cause an action without changing a property. Like calling a method on the Model that has no inputs from the Presenter? Would you use a dummy bool for this sort of thing? like Set Started = True? and Started would set itself false? \$\endgroup\$ – HodlDwon Mar 15 '13 at 20:21
0
\$\begingroup\$

After some more research, I just wanted to add that the correct answer for my use-case and intuitions is actually the Presenter First variant of MVP (Model-View-Presenter). It's a design pattern that pivots from the Presenter to create references for both the Model and the View, rather than starting with the View and creating the Presenter which creates the Model.

Here's the snippet for the main thread entry methods of the Presenter First pattern

Code from C# Presenter First example – VS2003 project for a sliding puzzle game

using System;
using System.Windows.Forms;

namespace wsm.Puzzle
{
    /// <summary>
    /// Summary description for PuzzleMain.
    /// </summary>
    public class PuzzleMain
    {
        [STAThread]
        static void Main()
        {
            ILoadImageModel loadImageModel = new LoadImageModel();
            ILoadImageView loadImageView = new LoadImageDialog();
            new LoadImagePresenter(loadImageModel, loadImageView);

            IImageCutter imageCutter = new ImageCutter();

            PuzzleForm view = new PuzzleForm();
            IPuzzleModel model = new PuzzleModel(loadImageModel, imageCutter);
            new PuzzlePresenter(model, view);

            model.Initialize();

            Application.Run(view);
        }
    }
}

The pattern plays very well into my kind of scenarios where my controls come from PLCs (Programmable Logic Controller), sensors, cylinders, a user Form, etc... typically I deal as much or more with machine IO, than I do with humans/operators. Intuitively this pattern just feels better and makes more sense for my particular scenarios.

I should also note that Steven Doggart and Dan Lyons were both correct about the irrelevance of multithreading in my original question. The proposed question actually missed the point of the real problem I had.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.