Active Objects Pattern Futures with .NET Framework Task Parallel Library

Active Object is a common pattern for hiding access to concurrent data structures and simplifying an object's interface. Task Parallel Library (TPL) includes all the structures needed to build an Active Object.

Of course concurrency cannot always be completely hidden from a consuming client. For example, how does a method on an Active Object handle a return value to the client when the value is being generated somewhere in a Threadpool? The Active Object Pattern guidelines recommend using a Future. In TPL a Future is implemented using the Task class. (See Understanding Tasks in .NET Framework 4.0 Task Parallel Library).

There are multiple TPL approaches to implementing the Active Object Pattern's Future. A few of those approaches will be examined in this article.

Active Object Overview

The Active Object Pattern has the following conventions:

  • It has a Proxy supporting client interaction.
  • It includes Request messages that encapsulate the desired Proxy invocation.
  • A Scheduler 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 performs the execution according to the Scheduler's Request message.
  • The Active Object can return a value using a callback mechanism like a Future. This scenario is the focus of this article.

The graphic below depicts the interaction between the components above.

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

As stated earlier the article will focus on different approaches to implementing the Future return value in TPL.

Wait in the Proxy

The first approach is to leverage a Task inside of the Active Object Proxy. A client using the application blocks until the operation completes. A timeout on the Task.Wait ensures that the client will not block forever. Sample code for the approach appears below.

            var d = proxy.Print_Synchronous("Some data 1");
            Console.WriteLine("Return Synchronously " + d);
 
        public string Print_Synchronous(string data)
        {
            var t = ProcessScheduler.ScheduleCompletion(new ExecuteRequest(Guid.NewGuid().ToString(), data));
 
            t.Wait(1000);
 
            return t.Result;
        }

A benefit of this approach is the client never deals with concurrency unless the Task.Wait times out. The Proxy must choose how to handle the generated Exception. Task Exceptions are packaged in an AggregateException. A complete review of the AggregateException is beyond the scope of this article.

The approach works well if the operation is expected to have a maximum duration. To handle a variable timeout; durations can be configured in a configuration file or passed into the method.

On the downside; the operation blocks the client. If blocking is not desirable a developer can return a Task class to the client.

Task

A complete introduction to the Task class is beyond the scope of this article, but there is an introduction here: Understanding Tasks in .NET Framework 4.0 Task Parallel Library. The Task sample appears below.

            var t2 = proxy.Print_Task(Guid.NewGuid().ToString(),"Some data 3");
 
            Task.WaitAll(new Task[2] { t1, t2 });
 
 
        public Task<string> Print_Task(string jobId, string data)
        {
            return ProcessScheduler.ScheduleTask(new ExecuteRequest(jobId, data));
        }
 
        public static Task<string> ScheduleTask(ExecuteRequest request)
        {
            var job = new JobTask(request, _servant);
 
            return job.Run();
        }
 
    internal class JobTask
    {
        private ExecuteRequest _request = null;
        private Servant _servant = null;
 
        public JobTask(ExecuteRequest request, Servant servant)
        {
            _request = request;
            _servant = servant;
        }
 
        public Task<string> Run()
        {
            return Task.Factory.StartNew<string>(() =>
            {
                return _servant.Execute(_request);
            }
            );
 
        }
    }

Print_Task returns a Task<string> class. A consuming client can Wait on the Task class or invoke a Continuation in response to a Task class. Unlike the synchronous example; the client can make multiple invocations and wait on completion of all the invoked Tasks.

With multiple Tasks a consuming client may have difficulty correlating the completed Task back to the originating invocation. A Guid, int, or a string all make good correlating identifiers. Unlike the sample, however, it may be more helpful to wrap the results and the identifier in another data structure.

A variation on the Task is to use a TaskCompletionSource inside the Active Object.

TaskCompletionSource

Like the Task example, the TaskCompletionSource sample returns a Task class. The TaskCompletionSource sample appears below.

            var t1 = proxy.Print_Completion(Guid.NewGuid().ToString(),"Some data 2");
 
            Task.WaitAll(new Task[2] { t1, t2 });
 
        public static Task<string> ScheduleCompletion(ExecuteRequest request)
        {
            var job = new JobCompletion(request, _servant);
 
            job.Run();
 
            return job.Completion.Task;
        }
 
    internal class JobCompletion
    {
        public TaskCompletionSource<string> Completion = new TaskCompletionSource<string>();
 
        private ExecuteRequest _request = null;
        private Servant _servant = null;
 
        public JobCompletion(ExecuteRequest request, Servant servant)
        {
            _request = request;
            _servant = servant;
        }
 
        public void Run()
        {
            Task.Factory.StartNew(() =>
            {
                _servant.Execute(_request, Completion);
            }
            );
 
        }
    }
 
public void Execute(ExecuteRequest request, TaskCompletionSource<string> completion)
{
    Console.WriteLine("Starting print " + request.Context + "...");
    Thread.Sleep(750);
 
 
    Console.WriteLine("Done printing " + request.Context + " " + request.Data + "...");
       
    completion.TrySetResult(request.Context);//only use this with the Async methods           
 
}

TaskCompletionSource allows a developer to control the completion and result of a Task class; rather than tying the Task result to the result of an Action delegate.

TaskCompletionSource is best used for the Asynchronous Programming Model (APM) or another .NET operation that either behaves asynchronously or executes in its own Thread. Rather than tying up a Thread in the Threadpool; an APM operation includes the TaskCompletionSource in the operation state.

Cancellation

No TPL article would be complete without mentioning Cancellation. Aborting running operations require a CancellationToken and an Action delegate checking for the cancellation status. There are many ways a consuming client could cancel the work. For example: the Active Object could include an additional Cancel method that accepts the Task object and maps the Task back to a CancellationToken.

A complete review of Cancellations is beyond the scope of this article; but for a good resource, see Understanding .NET Framework Task Parallel Library Cancellations.

Conclusion

Active Object is a common pattern for hiding access to concurrent data structures and simplifying an object's interface. Active Object implementation difficulties arise when a method must return a value back to the client via a Future or Callback. Task Parallel Library is equipped with Tasks and TaskCompletionSource object to ease the difficulty.

Resources

"Active Object: An Object Behavioral Pattern for Concurrent Programming"

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

"Prefer Using Futures or Callbacks to Communicate Asynchronous Results"



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

  • Live Event Date: May 6, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT While you likely have very good reasons for remaining on WinXP after end of support -- an estimated 20-30% of worldwide devices still are -- the bottom line is your security risk is now significant. In the absence of security patches, attackers will certainly turn their attention to this new opportunity. Join Lumension Vice President Paul Zimski in this one-hour webcast to discuss risk and, more importantly, 5 pragmatic risk mitigation techniques …

  • As businesses have grown in size and global reach, emerging technologies such as cloud computing and virtualization have appeared to help companies effectively connect and grow. However, the networking strategies and infrastructures that keep organizations connected have often remained in the past. Now, new strategies that leverage global connectivity and locations exist to provide a more flexible and cost-effective alternative to traditional networking systems. This Aberdeen report analyzes how top performing …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds