.NET Framework Task Parallel Library and the Active Objects Pattern

Hiding an asynchronous or parallel process behind a seemingly simple synchronous method call is common practice for a developer trying to simplify an object's usability. Task Parallel Library (TPL) is a good choice for the Parallel Computing data structures often required for such a process. However, like all development, following patterns and conventions eases the implementation burden and improves code supportability.

Active Object is a common pattern for hiding access to concurrent data structures and simplifying an object's interface. Like all patterns Active Object is a prescriptive set of guidelines. A TPL Active Objects Pattern implementation follows in the paragraphs below.

Active Object Overview

Developers may have many motivations for hiding concurrency. One common motivation for employing an Active Object is: a developer may be optimizing some existing class using concurrency and may not want to change how the class is employed. Whatever the motivation, the pattern remains the same and consists of the following elements:

  • A Proxy supporting client interaction
  • A Request message the Proxy creates to encapsulate the desired invocation
  • A Scheduler that receives the Request messages from the Proxy and maintains a Request message Queue. The Scheduler runs on a Thread separate from the client containing the Proxy and handles execution scheduling.
  • A Servant performing the execution according to the Scheduler's Request message
  • An optional return value or callback mechanism like a Task class or Future. This scenario is beyond the scope of this article.

The graphic below depicts the interaction between the components above.


Figure 1: Source: "Active Object An Object Behavioral Pattern for Concurrent Programming" http://www.cs.wustl.edu/~schmidt/PDF/Act-Obj.pdf

Aside from the Threading and other components required to schedule work; a shared data structure like the Request queue would require guarded access so queuing Request messages don't collide with dequeuing requests.

Luckily, TPL sports data structures that handle all challenges posed by the Active Object implementation.

The sample Active Object implementation is a Console application that utilizes a Thread.Sleep to simulate a workload and prints a string from the client to the Console. The client invocation code appears below.

            var proxy = new Proxy();
 
            proxy.Print("Some data 1");
            proxy.Print("Some data 2");
            proxy.Print("Some data 3");
 
            Console.WriteLine("Press any key to continue...");
            Console.ReadKey();

Some sample output appears below:

Press any key to continue...
Starting print 158e6aac-3ca1-46d4-b68c-2e2c2713a6ac...
Starting print 06f0b203-c9e0-417a-97fc-bc76926e5dc0...
Done printing 158e6aac-3ca1-46d4-b68c-2e2c2713a6ac Some data 1...
Starting print aaba0ab4-11ae-438f-b174-197ae944a167...
Done printing 06f0b203-c9e0-417a-97fc-bc76926e5dc0 Some data 2...
Done printing aaba0ab4-11ae-438f-b174-197ae944a167 Some data 3...

As stated earlier, the client consumes a Proxy class.

Proxy

The Proxy is the client interface. Proxy handles gathering client input and creating the Request message. The sample Proxy and Request classes appear below.

    internal class ExecuteRequest
    {
        public ExecuteRequest(string context, string data, Action<int> progress=null)
        {
            Context = context;
            Data = data;
            Progress = progress;
        }
 
        public string Context { get; set; }
        public string Data { get; set; }
 
    }
 
    public class Proxy
    {
        public void Print( string data)
        {
            ProcessScheduler.Schedule(new ExecuteRequest(Guid.NewGuid().ToString(), data));
        }
 
    }

A Proxy may create different messages or include different message contents according to the method invocation. The Active Object pattern dictates using message passing instead of, for example, a delegate. Though a developer could opt out of the pattern and use a delegate; it's important to understand that message passing is a safer concurrency mechanism than a delegate.

The Proxy posts the message to a queue residing inside the Scheduler.

Scheduler and Servant

As stated earlier, the Scheduler manages a queue of Request messages and handles scheduling the work execution. The Scheduler sample code is below:

    internal class Job
    {
        public TaskCompletionSource<string> Completion = new TaskCompletionSource<string>();
 
        private ExecuteRequest _request = null;
        private Servant _servant = null;
 
        public Job(ExecuteRequest request, Servant servant)
        {
            _request = request;
            _servant = servant;
        }
 
        public void Run()
        {
            Task.Factory.StartNew(() =>
            {
                _servant.Execute(_request, Completion);
            }
            );
 
        }
    }
 
    internal class ProcessScheduler
    {
        private static Servant _servant = new Servant();
 
        public static void Schedule(ExecuteRequest request)
        {
            var job = new Job(request, _servant);
 
            job.Run();
 
        }
 
    }

Between the TaskScheduler class, BlockingCollection, and the Task class, TPL already includes most of the plumbing required to implement this class.

BlockingCollection makes an ideal queue. Restricting collection size allows throttling and the collection balances performance with concurrency safety.

The Sample utilizes a Job class to package the Servant with the Request. Job class is not part of the pattern, but rather it's an implementation detail in the sample.

As stated earlier the Servant carries out the Request. The Servant code appears below:

    internal class Servant
    {
   
        public void Execute(ExecuteRequest request, TaskCompletionSource<string> completion)
        {
            Console.WriteLine("Starting print " + request.Context + "...");
            Thread.Sleep(750);
 
            for (int n = 0; n < 4; ++n)
            {
                if (request.Progress != null) { request.Progress(n); }
            }
 
            Console.WriteLine("Done printing " + request.Context + " " + request.Data + "...");
       
 
        }
    }

Conclusion

Active Object is a common pattern for hiding access to concurrent data structures and simplifying an object's interface. Task Parallel Library contains all the plumbing a developer will need to implement the pattern.

Resources

"Know When to Use an Active Object Instead of a Mutex"

"Prefer Using Futures or Callbacks to Communicate Asynchronous Results"

"Active Object An Object Behavioral Pattern for Concurrent Programming"



About the Author

Jeffrey Juday

Jeff is a software developer specializing in enterprise application integration solutions utilizing BizTalk, SharePoint, WCF, WF, and SQL Server. Jeff has been developing software with Microsoft tools for more than 15 years in a variety of industries including: military, manufacturing, financial services, management consulting, and computer security. Jeff is a Microsoft BizTalk MVP. Jeff spends his spare time with his wife Sherrill and daughter Alexandra.

Related Articles

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Event Date: April 15, 2014 The ability to effectively set sales goals, assign quotas and territories, bring new people on board and quickly make adjustments to the sales force is often crucial to success--and to the field experience! But for sales operations leaders, managing the administrative processes, systems, data and various departments to get it all right can often be difficult, inefficient and manually intensive. Register for this webinar and learn how you can: Align sales goals, quotas and …

  • "Security" is the number one issue holding business leaders back from the cloud. But does the reality match the perception? Keeping data close to home, on premises, makes business and IT leaders feel inherently more secure. But the truth is, cloud solutions can offer companies real, tangible security advantages. Before you assume that on-site is the only way to keep data safe, it's worth taking a comprehensive approach to evaluating risks. Doing so can lead to big benefits.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds