Building Task Based WCF Services with Task Parallel Library

Tasks and the Task Parallel
Library
(TPL) will soon be entering the Windows
Communication Foundation
(WCF) vernacular. Upcoming .NET language features will be
replacing the outdated Asynchronous Programming Model (APM). WCF 4.5 will
include Task based options. However, a WCF developer needn’t wait for .NET 4.5
to leverage TPL. TPL Tasks shipped with .NET 4.0 and WCF 4.0 works with TPL. There
are some tricks to squeezing out all potential performance and avoiding
concurrency problems. Building a TPL Task based WCF Service is demonstrated in
the paragraphs that follow.

Tasks and the .NET Future

Realizing that CPUs were becoming more numerous and
concurrency was going to become more important; Microsoft built TPL to make concurrency
easier. TPL helps a developer partition and execute an Application workload
on multiple CPUs. An Application workload is partitioned into Tasks classes.
The Task class is the TPL core.

Unquestionably Tasks will become more important in future
.NET versions. As stated earlier, Tasks will begin to play a larger role in
the next versions of the .NET Framework. A new Async language feature is built
on Tasks. A new asynchronous programming model centered on Tasks will become
part of everything in the .NET Framework.

Typically, a Task is not something that executes for too
long nor should it be a single line of code. There are two Task types: a
generic Task that returns a Result and a Task that returns no result. Since
code can fault, Tasks include Exception handling support. Tasks also support
operation cancellation. Some Properties and Methods on the Task class appear
below.

    public class Task : IAsyncResult, IDisposable
    {
        public Task(Action action);
        public Task(Action<object> action, object state);
        public Task(Action action, CancellationToken cancellationToken);
        public object AsyncState { get; }
        public TaskCreationOptions CreationOptions { get; }
        public static int? CurrentId { get; }
        public AggregateException Exception { get; }
        public int Id { get; }
        public bool IsCompleted { get; }
        public bool IsFaulted { get; }
        public TaskStatus Status { get; }
        public Task ContinueWith(Action<Task> continuationAction);
 
        public void Start();
        public void Wait();
        public void Wait(CancellationToken cancellationToken);
        public bool Wait(int millisecondsTimeout);
 
        public static void WaitAll(params Task[] tasks);
 

Tasks have other nice features. Developers can block and
wait for multiple Tasks to complete before proceeding. Demonstrated later in
the article are Continuations. Continuations execute a Task contingent on
completing an “antecedent” Task.

Getting TPL working properly with WCF requires understanding
WCF Concurrency. WCF Concurrency is housed in the ServiceBehaviors attribute.

WCF ServiceBehaviors

One of the early decisions a WCF developer makes is what to
configure on a Service’s ServiceBehavior attribute. Of particular interest is
what to set Concurrency and Instancing levels to. Does a Service spin-up a new
Service class instance each time a service is activated or should a Service use
a single instance? WCF Service Instancing is mostly controlled by the
ServiceBehavior InstanceContextMode and ConcurrencyMode. The ServiceBehavior configuration
from the sample application appears below.

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode=ConcurrencyMode.Multiple)]
public class TestServiceService : Test.WCFTPL.Server.ITestServiceContract
{
    public TestServiceService()
    {
        //Change InstanceContextMode to PerCall and this gets called multiple times
        Console.WriteLine("Called TestService Constructor");
    }
 
}
 

While a developer could use Tasks regardless of the
ServiceBehavior settings it’s important to understand ServiceBehavior, Instancing,
and Concurrency dangers. One fatal Concurrency mistake developers make is
improperly sharing the same piece of memory. The ServiceBehavior settings
above push more instance handling into the Service Operations. Local variables
almost eliminate improper sharing. Since only one instance of the Service
exists a developer is forced to do more allocation locally in the Operation.
For example: Choosing PerSession Instancing leaves open the temptation to share
member variables inside a class Instance. Since many WCF Services have more
than one operation; that means two Operations running concurrently against the
same Session Instance may be unknowingly sharing a space in memory.

Properly configured ServicesBehaviors and Service
architectural decisions are important to building a Task Based WCF Service. Full
Task based Service benefits will not be realized unless a developer implements
the WCF Asynchronous model.

Asynchronous WCF with Tasks

Typically, a WCF Service with Asynchronous Operations will
be more per formant than the same Service doing Synchronous Operations.
Asynchronous, when done properly, will typically tie up less IO and respond
better to a workload. Unfortunately, the Asynchronous Programming Model (APM)
WCF follows is difficult to follow. Most developers opt out of Asynchronous
unless Asynchronous is the only route.

The Service interface implementation from the sample appears
below.

[ServiceContract(Namespace = "Test.WCFTPL.Service")]
interface ITestServiceContract
{
    //Callback and state must be the last two.  REMEMBER parameters must match with the client interface
 
    [OperationContractAttribute(AsyncPattern = true, Action = "TestServiceMethod", Name = "TestServiceMethod", ReplyAction = "TestServiceMethodReply")]
    IAsyncResult BeginTestServiceMethod(RequestObj req, AsyncCallback callback, object asyncState);
 
    // Note: There is no OperationContractAttribute for the end method.
    ResponseObj EndTestServiceMethod(IAsyncResult result);
 
}
 

Only the Begin portion needs the OperationContract Attribute.
The following code demonstrates doing Asynchronous with Tasks.

    #region ITestServiceContract Members
 
    public IAsyncResult BeginTestServiceMethod(RequestObj req, AsyncCallback callback, object asyncState)
    {

        var task = new Task<ResponseObj>((state) =>
            {
                SpinWait.SpinUntil(() => { return false; }, 1000);
 
                return new ResponseObj() { PayloadOriginal = req.Payload, PayloadResponse = "Response was " + Guid.NewGuid().ToString() };
            }
        ,asyncState);
 
        //When the task completes notify the callback
        task.ContinueWith((t) => { callback(t); });
        task.Start();
 
        return task;
    }
 
    public ResponseObj EndTestServiceMethod(IAsyncResult result)
    {
        var task = (Task<ResponseObj>)result;
 
        return task.Result;
    }
 
    #endregion
 

Admittedly this is still more complicated than the
synchronous model. However, the Task implementation is more self-contained, a
bit easier to follow, and a little less clumsy.

The Task delegate is contained within a Closure. Task.Start
does not immediately execute the Task. Instead, Start queues the Task to run
in the .NET Thread Pool. Task implements the IAsyncResult interface. IAsyncResult
is the core of the Asynchronous Programming Model. Creating the task with the
object state parameter tucks the State away inside the Task class until it is needed
again for the “End” portion in the operation.

Continuations are the key to making Asynchronous easier. Continuations
are invoked when the Task they’re attached to completes. A complete review of
Continuation options is beyond the scope of this article, but you’ll find more
under the resource sections. When the task completes, the Continuation Task is
scheduled to run. The Continuation invokes the callback and the WCF
infrastructure, in turn, invokes the End Method.

End method unpacks the Result property from the completed
Task and returns the result. WCF takes over from there, pushing the serialized
class out of the Service operation.

Conclusion

Asynchronous WCF code is more per formant than synchronous
code. Tasks and the Task Parallel Library can make handling Asynchronous code
easier. However, before using Tasks with WCF a developer should understand how
WCF configurations can impact a Task based solution.

Resources

“Sessions,
Instancing, and Concurrency”

“What’s
new in .NET 4.5 for Parallelism”

“Understanding
Tasks in .NET Framework 4.0 Task Parallel Library”

Acknowledgements

Special thanks to Stephen Toub with Microsoft.

More by Author

Must Read