Click here to Skip to main content
15,898,900 members
Articles / Programming Languages / C#

Multi Threading with Generic Helper in C#

Rate me:
Please Sign up or sign in to vote.
4.00/5 (1 vote)
10 Jun 2012CPOL3 min read 13.4K   12   2
Multi threading with generic helper in C#

"Threading enables your C# program to perform concurrent processing so you can do more than one operation at a time. For example, you can use threading to monitor input from the user, perform background tasks, and handle simultaneous streams of input." 

Reference: http://msdn.microsoft.com/en-us/library/ms173178(v=vs.80).aspx

In other words, it's a beautiful thing. It can be one of your best allies in terms of performance if used correctly (when needed) or one of the biggest headaches to debug. Personally, I love threading! Buuuuut my fellow colleagues would say I have more faith in it than I should.

Currently I am working on the new backend core architecture for our newest product. It's an MVC application that will be able to run on any device (mobile, tablet, desktop, phablet, etc...), offer multilanguage support, and will have all the bells and whistles that new code can give your application (performance-wise) if you had the chance to write it all over again.

That being said, as part of the core architecture I have a set of tools, factories and utilities available for our other developer's to use. Ideally, I'd like to keep this as an N-Tier application and have our other developers use the core library for all the needs of any tier or app we have leaving the core to the R&D Team. These utilities and factories include functionality for Maps, Globalization, Diagnostics, Type Extensions, IO, Messaging, Runtime Tasks, DA, Data Manipulation, Caching, Attributes, Logging, Models, etc...

One of the coolest utilities that came out from all this new architecture work was a wrapper I created over the System.Threading.ThreadPool. This would give our developers a central place to safely run and implement multiple tasks at once, relying on Microsoft's QueueUserWorkItem and C# Generics. You can give the ThreaderPool any declared in/out type or just an out type, and assign it any Action you want.

Example, let's say you have a quoting system and you have three different requests that you need to send off to your engine to get your responses. Simply assign the method to call, and add your QuoteRequest to the pool, and run the method. This will return the results in the same order as entered.

C#
using (ThreaderPool<QuoteRequest, QuoteResponse> 
pool = new ThreaderPool<QuoteRequest, QuoteResponse>(q.GetQuote))
{
    pool.Add(request1);
    pool.Add(request2);
    pool.Add(request3);

    pool.Run();

    List<QuoteResponse> responses = pool.Results;
}

Or, let's say you have different methods to call with either the same or different requests. A method for different types of responses.

C#
using (ThreaderPool<QuoteRequest, 
QuoteResponse> pool = new ThreaderPool<QuoteRequest, QuoteResponse>())
{
    pool.Add(q.GetQuote, request1);
    pool.Add(q.GetQuoteLTL, request2);
    pool.Add(q.GetQuote, request3);
  
    pool.Run();

    List<QuoteResponse> responses = pool.Results;
}

It's literally that simple. This baby comes with exception handling too.

C#
catch(ThreaderPoolRunException<QuoteRequest> ex)
{
    Console.WriteLine(ex.Message);
    foreach (varitem in ex.Exceptions)
    {
        QuoteRequest qr = item.Key;
        Exception e = item.Value;

        Console.WriteLine(e.Message);
        Console.WriteLine("For request with number {0}\r\n\r\n", qr.Number);
    }
}

List<QuoteResponse> responses = pool.Results;

As part of a Dev Samples page, I was showing everyone an example of each custom component we were writing too and I used this pool as an example of how to use it in MVC development. Now before you guys say anything, I know there's the Async controller to do this kind of work. But this was created before I knew about that guy. So another example.

C#
public ActionResultSamples()
{
    DevSamplesModel model = newDevSamplesModel();
    ActionResult result = View(model);
 
    using (ThreaderPool<ComponentBase> pool = newThreaderPool<ComponentBase>())
    {
        pool.Add(GetButtonModel);
        pool.Add(GetCalendarModel);
        pool.Add(GetDropDownListModel);
        pool.Add(GetGridModel);
        pool.Add(GetHtmlEditorModel);
        pool.Add(GetMemoModel);
        pool.Add(GetMenuModel);
        pool.Add(GetNavBarModel);
        pool.Add(GetRadioButtonListModel);
        pool.Add(GetRoundPanelModel);
        pool.Add(GetTrackBarModel);
        pool.Add(GetTreeViewModel);
        pool.Add(GetWindowModel);
        pool.Add(GetCheckBoxModel);
        pool.Add(GetCheckBoxListModel);
        pool.Add(GetLabelModel);
        pool.Add(GetImageModel);
        pool.Add(GetProgressBarModel);
        pool.Add(GetListBoxModel);

        ThreadOperations operations = new ThreadOperations();
        operations.Assign(
            onError: (e) =>
            {
                RoundPanelModel panelModel = new RoundPanelModel("pnlError");

                panelModel.HeaderText = "Error";
                panelModel.Content = "Oops!  ";
                foreach (Exceptionexception in e.Exceptions)
                {
                    panelModel.Content += exception.Message + "<br />";
                }

               result = PartialView(UIView.Components.RoundPanel, panelModel);
            }
        );

        pool.Run(operations);

        List<ComponentBase> responses = pool.Results;
        if (responses != null)
        {
            model.ButtonModel = responses.FirstOrDefault
            (r => r != null && r.GetType().Name == "ButtonModel") asButtonModel;
            model.CalendarModel = responses.FirstOrDefault
            (r => r != null && r.GetType().Name == "CalendarModel") as CalendarModel;
            model.DropDownListModel = responses.FirstOrDefault
            (r => r != null && r.GetType().Name == "DropDownListModel") as DropDownListModel;
            model.GridModel = responses.FirstOrDefault
            (r => r != null && r.GetType().Name == "GridModel") asGridModel;
            model.HtmlEditorModel = responses.FirstOrDefault
            (r => r != null && r.GetType().Name == "HtmlEditorModel") as HtmlEditorModel;
            model.MemoModel = responses.FirstOrDefault
            (r => r != null && r.GetType().Name == "MemoModel") asMemoModel;
            model.MenuModel = responses.FirstOrDefault
            (r => r != null && r.GetType().Name == "MenuModel") asMenuModel;
            model.NavBarModel = responses.FirstOrDefault
            (r => r != null && r.GetType().Name == "NavBarModel") asNavBarModel;
            model.RadioButtonListModel = responses.FirstOrDefault
            (r => r != null && r.GetType().Name == "RadioButtonListModel") as RadioButtonListModel;
            model.RoundPanelModel = responses.FirstOrDefault
            (r => r != null && r.GetType().Name == "RoundPanelModel") as RoundPanelModel;
            model.TrackBarModel = responses.FirstOrDefault
            (r => r != null && r.GetType().Name == "TrackBarModel") as TrackBarModel;
            model.TreeViewModel = responses.FirstOrDefault
            (r => r != null && r.GetType().Name == "TreeViewModel") as TreeViewModel;
            model.WindowModel = responses.FirstOrDefault
            (r => r != null && r.GetType().Name == "WindowModel") asWindowModel;
            model.CheckBoxModel = responses.FirstOrDefault
            (r => r != null && r.GetType().Name == "CheckBoxModel") as CheckBoxModel;
            model.CheckBoxListModel = responses.FirstOrDefault
            (r => r != null && r.GetType().Name == "CheckBoxListModel") as CheckBoxListModel;
            model.LabelModel = responses.FirstOrDefault
            (r => r != null && r.GetType().Name == "LabelModel") asLabelModel;
            model.ImageModel = responses.FirstOrDefault
            (r => r != null && r.GetType().Name == "ImageModel") asImageModel;
            model.ProgressBarModel = responses.FirstOrDefault
            (r => r != null && r.GetType().Name == "ProgressBarModel") as ProgressBarModel;
            model.ListBoxModel = responses.FirstOrDefault
            (r => r != null && r.GetType().Name == "ListBoxModel") asListBoxModel;
        }
    }

    return result;
}

Below is a view of the ThreaderPool class to give you an idea of how I accomplished this. So far, after much load testing, it hasn't been the point of any failure and has greatly improved the speed on some back end architecture work.

To view the rest of the files, click the Download button below.

Note: This was originally from my own code library Imfaqncodn.

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

namespace Imfaqncodn.Runtime.Threading
{
    public sealed class ThreaderPool<In, Out> : IDisposable// where Out : new()
    {
        internal static List<ThreadPoolCounter> ThreadPoolCounters { get; set; }
        internal static object action_locker = newobject();

        private List<ThreaderPoolAction<In, Out>> Actions { get; set; }
        private ThreaderPoolAction<In, Out>.ThreaderPoolActionEventHandler E { get; set; }

        public List<Out> Results { get; set; }

        public ThreaderPool() { }
        public ThreaderPool(ThreaderPoolAction<In, Out>.ThreaderPoolActionEventHandler e)
            : base()
        {
            this.E = e;
        }

        public void Add(In i)
        {
            Add(this.E, i);
        }
        public void Add(ThreaderPoolAction<In, Out>.ThreaderPoolActionEventHandler e, In i)
        {
            if (Actions == null)
            {
                Actions = new List<ThreaderPoolAction<In, Out>>();
            }

            ThreaderPoolAction<In, Out> action = new ThreaderPoolAction<In, Out>(i);
            action.OnThreaderPool += e;

            Actions.Add(action);
        }

        public void Run(ThreadOperations<In> operations = null)
        {
            if (Actions != null)
            {
                //ManualResetEvent[] events = new ManualResetEvent[Actions.Count()];

                Guid guid = Guid.NewGuid();

                using (ThreadPoolCountercounter = new ThreadPoolCounter()
                    {
                        Identifier = guid
                        ,
                        NumberOfThreadsNotYetCompleted = Actions.Count()
                        ,
                        Event = new ManualResetEvent(false)
                    })
                {
                    if(ThreadPoolCounters == null)
                    {
                        ThreadPoolCounters = new List<ThreadPoolCounter>();
                    }

                    ThreadPoolCounters.Add(counter);

                    try
                    {
                        int i = 0;

                        foreach (var action in Actions)
                        {
                            //events[i] = new ManualResetEvent(false);

                            //action._doneEvent = events[i];
                            action.Identifier = counter.Identifier;

                            i++;

                            ThreadPool.QueueUserWorkItem(action.ThreadPoolCallback, action.Request);
                        }

                        // Wait for all threads in pool to run.
                        //WaitHandle.WaitAll(events);
                        
                        counter.Event.WaitOne();

                        //now combine our results
                        this.Results = new List<Out>();

                        foreach (var action in Actions)
                        {
                            this.Results.Add(action.Response);
                        }

                        if (Actions.Any(a => a.exception != null))
                        {
                            ThreaderPoolRunException<In> 
                            exceptions = new ThreaderPoolRunException<In>();

                            foreach (var action in Actions.Where(a => a.exception != null))
                            {
                                exceptions.Exceptions.Add(action.Request, action.exception);
                            }

                            operations.onError(exceptions);
                        }
                    }
                    finally
                    {
                        ThreadPoolCounters.Remove(counter);
                    }
                }
            }
        }

        #regionIDisposable Members

        private bool disposed = false;
        /// <summary>
        /// This object will be cleaned up by the Dispose method.
        /// Therefore, you should call GC.SupressFinalize to
        /// take this object off the finalization queue
        /// and prevent finalization code for this object
        /// from executing a second time.
        /// </summary>
        void IDisposable.Dispose()
        {
            Dispose();
        }
        /// <summary>
        /// This object will be cleaned up by the Dispose method.
        /// Therefore, you should call GC.SupressFinalize to
        /// take this object off the finalization queue
        /// and prevent finalization code for this object
        /// from executing a second time.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);

            GC.SuppressFinalize(this);
        }

        // Dispose(bool disposing) executes in two distinct scenarios.
        // If disposing equals true, the method has been called directly
        // or indirectly by a user's code. Managed and unmanaged resources
        // can be disposed.
         // If disposing equals false, the method has been called by the
        // runtime from inside the finalizer and you should not reference
        // other objects. Only unmanaged resources can be disposed.
        protected voidDispose(bool disposing)
        {
            // Check to see if Dispose has already been called.
            if (!this.disposed)
            {
                // If disposing equals true, dispose all managed
                // and unmanaged resources.
                if (disposing)
                {
                    // Dispose managed resources.
                    this.Actions = null;
                    this.E = null;
                    this.Results = null;
                }

                // Note disposing has been done.
                disposed = true;

            }
        }

        #endregion
    }
}

This article was originally posted at http://dillonraney.blogspot.com/feeds/posts/default

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionHarms Scalability Pin
InfRes11-Jun-12 21:31
InfRes11-Jun-12 21:31 
QuestionConsumer code? Pin
bobfox11-Jun-12 10:37
professionalbobfox11-Jun-12 10:37 
You should provide an example showing how to consume your code.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.