Critical Development

Language design, framework development, UI design, robotics and more.

New Spin on Spawning Threads

Posted by Dan Vanderboom on June 23, 2008

There are several good libraries (PFX, PLinq, CCR, etc.) available and coming out soon that help to write concurrent logic, providing various abstractions and control patterns.  Until I can incorporate these into my Compact Framework applications, however, I find myself spinning up threads in the usual way, and sometimes what I need to run concurrently doesn’t require any fancy coordination.

Here is the thread-spawning pattern I’ve used, and have seen used, countless times:

protected override void OnStart()
{
    Thread t = new Thread(new ThreadStart(Go));
    t.Name = "PrefetchLookupData";
    t.IsBackground = true;
    t.Priority = ThreadPriority.AboveNormal;
    t.Start();
}

private void Go()
{
    var c1 = LookupListDA.CustomerTypes;
    var c2 = LookupListDA.SalesOrderStates;
    var c3 = LookupListDA.VendorTypes;
}

The data layer shown in the example caches each lookup list internally the first time its property getter is accessed, so we can assign them to variables that are never used here and effectively “pre-fetch” our lookup list data while the user is waiting in the main menu, for example.

Using a lambda statement (anonymous method), we can consolidate this into a single named method, and avoid cluttering our class with a potential plethora of methods that may perform some ad hoc background thread processing.

protected override void OnStart()
{
    Thread t = new Thread(new ThreadStart(() =>
    {
        var c1 = LookupListDA.CustomerTypes;
        var c2 = LookupListDA.SalesOrderStates;
        var c3 = LookupListDA.VendorTypes;
    }));

    t.Name = "PrefetchLookupData";
    t.IsBackground = true;
    t.Priority = ThreadPriority.AboveNormal;
    t.Start();
}

After a little reflection, I came up with a similar pattern that further encapsulates the details, and in doing so, created an abstraction called LogicStream.  Other names that work are ExecutionStream or CodeStream, with the central concept being that you’re specifying a sequential set (stream) of instructions that runs parallel to the stream that defines it.

protected override void OnStart()
{
    new LogicStream("PrefetchLookupData", s =>
        {
            var c1 = LookupListDA.CustomerTypes;
            var c2 = LookupListDA.SalesOrderStates;
            var c3 = LookupListDA.VendorTypes;
        });
}

Like the second example, this approach doesn’t require a separate method, but it’s also completely self-contained: there is no thread manipulation code outside of this code block; any setup we need to do can either be done via parameters (“PrefetchLookupData” names the thread) or by specifying an additional code block for thread configuration (an example is shown below).  Finally, the LogicStream constructor immediately invokes the thread, which is a background thread by default, so nothing additional needs to be done.

Here’s a slightly more complicated example, showing how you can configure the thread and pass in multiple parameters.

using System;
using System.Threading;

namespace Demo
{
    class Program
    {
        static void Main(string[] args)
        {
            Thread.CurrentThread.Name = "Main";

            new LogicStream("ThreadX", s =>
            {
                // rename parameters passed into thread
                string ExtraInfo = s.Get<string>(0);
                int StartIndex = s.Get<int>(1);
                int EndIndex = s.Get<int>(2);

                // do work
                for (int x = StartIndex; x <= EndIndex; x++)
                {
                    Console.WriteLine("x = " + x.ToString().PadRight(10) + s.Name);
                    Thread.Sleep(50);

                    // this code has access to the Thread object via the LogicStream
                    if (x > 90)
                        s.Thread.Abort();
                }
            }, "Width Data", 0, 100); // optional parameters passed into the LogicStream

            new LogicStream(t =>
                {
                    // configure thread with complete flexibility
                    t.Name = "ThreadY";
                    t.IsBackground = true;
                    t.Priority = ThreadPriority.Highest;
                    t.SetApartmentState(ApartmentState.MTA);
                },
                s =>
                {
                    for (int y = 0; y < 100; y++)
                    {
                        Console.WriteLine("y = " + y.ToString().PadRight(10) + s.Name.PadLeft(15));
                        Thread.Sleep(50);
                    }
                });

            new LogicStream("ThreadZ", ThreadPriority.Lowest, s =>
                {
                    for (int z = 0; z < 100; z++)
                    {
                        Console.WriteLine("z = " + z.ToString().PadRight(10) + s.Name.PadLeft(23));
                        Thread.Sleep(50);
                    }
                });

            Console.ReadKey();
        }
    }
}

When this program runs, three threads will fire up and start processing, but because all of the worker threads are background threads, at any time the console receives a key press, the application will exit.

The parameters passed into the first thread are constants, which of course could be defined within the LogicStream’s code block itself.  Because anonymous methods in C# create closures when necessary, you could use a variable defined within the method (in this case, Main), or make reference to the method’s argument, args, without needing to pass in any parameters at all.

Finally, here is the LogicStream class to make this work.

using System;

namespace System.Threading
{
    public class LogicStream
    {
        public Thread Thread { get; private set; }
        public object[] Parameters { get; set; }

        protected Action<LogicStream> Action;
        protected long InstanceID;
        protected  static long NextInstanceID = 0;

        public LogicStream(Action<LogicStream> Action, params object[] Parameters)
            : this("LogicStream" + InstanceID.ToString(), ThreadPriority.Normal, true, Action, Parameters) { }

        public LogicStream(string Name, Action<LogicStream> Action, params object[] Parameters)
            : this(Name, ThreadPriority.Normal, Action, Parameters) { }

        public LogicStream(string Name, ThreadPriority Priority, Action<LogicStream> Action, params object[] Parameters)
            : this(Name, Priority, true, Action, Parameters) { }

        public LogicStream(string Name, ThreadPriority Priority, bool IsBackground, Action<LogicStream> Action, params object[] Parameters)
        {
            this.Action = Action;
            this.Parameters = Parameters;

            InstanceID = NextInstanceID;
            Interlocked.Increment(ref NextInstanceID);

            Thread = new Thread(new ThreadStart(InvokeAction));
            Thread.Name = Name;
            Thread.Priority = Priority;
            Thread.IsBackground = IsBackground;
            Thread.Start();
        }

        public LogicStream(Action<Thread> Config, Action<LogicStream> Action, params object[] Parameters)
        {
            this.Action = Action;

            Thread = new Thread(new ThreadStart(InvokeAction));
            Config(Thread);

            InstanceID = NextInstanceID;
            Interlocked.Increment(ref NextInstanceID);

            Thread.Start();
        }

        protected void InvokeAction()
        {
            Action(this);
        }

        public T Get<T>(int ParameterIndex)
        {
            return (T)Parameters[ParameterIndex];
        }
    }
}

It’s very simple, but that’s the best part.  You have the choice to configure the thread properties through various constructor parameters, or by defining an anonymous configuration method that can line up nicely with your anonymous action method.  If you’re lazy and don’t want to name your thread, or if you’re spinning up a bunch of them to perform the same task, you’ll still get a thread name differentiated by an auto-incrementing InstanceID.

So this isn’t anything big, of course.  Just a little syntactic wrapper around Thread and ThreadStart classes, with simple patterns built in for passing and fetching parameters, and defining streams of parallel logic a little more intuitively and concisely.  The trick to concurrent programming is coordination among parallel threads: passing messages (posting to ports), serializing use of certain resources, and so on.

I spent much of yesterday watching all of the CCR videos again and studying that implementation, so I expect to be writing more about that in the near future.

Advertisements

6 Responses to “New Spin on Spawning Threads”

  1. If multiple threads could be creating instances of LogicStream at the same time, then “InstanceID++” should be “System.Threading.Interlocked.Increment(ref InstanceID);” and InstanceID{get;} should be synchronized.

  2. Dan Vanderboom said

    Thanks for the tip. The problem in my code was even worse. I was keeping track of the next ID to assign, but didn’t have a variable for that LogicStream’s current ID.
    I updated the code in the article. That ID is really only there to assign a low, zero-based integer to names that I assign to threads when a name isn’t explicitly supplied.

  3. Arjen Smits said

    In the first constructor of LogicStream, you mean NextInstanceID instance of InstanceID 😉

  4. yea.thanx for sharing this blog post with me.i just found it buy google couse i was searching for
    more info about this.Ang BANG! i found your blog that was so use full for me.
    feel free to drop me email i will send small donation.
    regards remonty krakow

  5. zhuzhu said

    Hello,

    Nice to be registered on dvanderboom.wordpress.com. My little name is maxizhu 😉

  6. You made a few nice points there. I did a search on the matter and found nearly all persons will consent with your blog.

Leave a Reply

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

WordPress.com Logo

You are commenting using your WordPress.com 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: