Exception Handling with Task Parallel Library Based WCF Services

Exception handling can be different across the .NET Framework.  Custom exceptions often augment the Exception base class.  In Task Parallel Library (TPL) Exceptions can pile-up fast; so TPL’s custom Exception, the AggregateException, includes properties developers use to handle numerous Exceptions.  Windows Communication Foundation (WCF) must often share Exceptions with a client on the other end of the network.  WCF includes custom exceptions for packaging and transporting a serialized Exception.

Bridging TPL and WCF exception handling models requires understanding both models.  The following paragraphs will demonstrate some techniques to bring the two models together.

WCF and TPL Together

Prior articles on this site demonstrated how these two technologies can play together.  How the technologies are combined influences how Exceptions handling is configured.  So here are some configuration assumptions.

Tasks are the TPL Core.  Tasks implement the IAsyncResult.  IAsyncResult is the Asynchronous Programming Model (APM) core.  A developer using TPL with WCF will likely utilize the WCF APM.  Signature sample WCF APM code follows.

public IAsyncResult BeginTestServiceMethod(RequestObj req, AsyncCallback callback, object asyncState)
{
          
    return new Func<ResponseObj> (
        () =>
        {
            throw new Exception("Test exception generated");
        }
    ).ToStartedTask<ResponseObj>(callback,asyncState);
           
}
 
public ResponseObj EndTestServiceMethod(IAsyncResult result)
{
    return result.ToResultOrException<ResponseObj>();
}
 

Notice the Begin portion that starts the operation and the End portion that returns the Result to the client.  A Callback method joins the functions together.  The Operation state is stored inside the Task instance. 

TPL Continuations glue the Task to the Callback.  I’ll talk more about Continuations later in the article.  The RunSynchronously option ensures that the Continuation is configured to run on the same thread as the Task.  Running synchronously is not a guarantee though.  If everything is operating normally this will be the case.  However if the Task has faulted, the Continuation will be scheduled on another Thread. 

Thus begins the Exception Odyssey.

TPL Exceptions

A complete TPL Exceptions review is beyond the scope of this article, so the overview follows.

As stated earlier TPL stores Exceptions in an AggregateException class.  An AggregateException is embedded in the Task Exception property.  The following code demonstrates accessing the Exception property.

if (t.IsFaulted)
{
    t.Exception.Handle((e) => { return true; });
    //Log to the Event log or some other place
}
 
task.Exception.Flatten().Message
 

Normally, uncaught Exceptions will terminate an application.  TPL handles Exceptions differently.  TPL requires an Exception to be observed.  So simply inspecting an Exception is enough to handle the exception.

Of course, most developers will want to do more than inspect an exception.  So, for example, a developer may want to capture an Exception Message and write it to the Event Log.  The previous TPL code demonstrates AggregateExeption properties to assist with this endeavor.

As mentioned earlier AggregateExceptions can become large.  A TPL operation waiting on a handful of faulting Tasks will accumulate a lot of Exceptions.  Special care must be taken with WCF to not only handle Exceptions, but also to properly package the Exceptions.

WCF Exception Contracts

WCF’s core comprises sending and receiving Messages.  WCF developers are careful about Message sizes.  Configuring a large Message size may unnecessarily consume system resources.  DataContracts dictate message formatting and message size.  WCF Exceptions are a sort of DataContract.  Services embed Exception Contracts alongside Data Contracts.  The following code demonstrates how to configure a WCF Exception Contract.

[OperationContractAttribute(AsyncPattern = true, Action = "TestServiceMethod", Name = "TestServiceMethod", ReplyAction = "TestServiceMethodReply")]
[FaultContract(typeof(TestServiceFaultContract))]
IAsyncResult BeginTestServiceMethod(RequestObj req, AsyncCallback callback, object asyncState);
 

The FaultContract attribute ensures that the Exception Contract will appear in the Services’ WSDL .

Large Exceptions sent back to a WCF client may mean large messages.  SOAP Services include special Exception formatting.  WCF hides this mess from the developer.   The following code demonstrates how to package a WCF Exception.

public static T ToResultOrException<T>(this IAsyncResult result)
{
    var task = result as Task<T>;
 
    if (task.IsFaulted)
    {
        throw new FaultException<TestServiceFaultContract>
            (new TestServiceFaultContract()
            {
                FaultCode = 1
                , FaultMessage = task.Exception.Flatten().Message }
                , new FaultReason("Because I felt like it"));
    }
 
    return task.Result;
}
 

All WCF Exceptions are packaged in a FaultException.  In the example; the code lives inside an Extension function that is attached to an IAsyncResult.  As stated earlier a Task is an IAsyncResult.  The Extension function standardizes how Results are propagated back to the Client.  If the Task is Faulted the Exception is packaged and an new FaultException is thrown.  Otherwise the Task’s Result is returned.  In most implementations there may be further Result packaging.

A client receiving the Exception deals only with another Exception class rather than wading through the XML.  Clients receive a CommunicationException, but can find more Exception information on properties throughout the custom Exception.  Some of those properties are demonstrated in the Client-side code below.

try
{
 
    var r = c.TestServiceMethod(new RequestObj() { Payload = "Test first load" });
 
    Console.WriteLine(obj.ToString() + " result was " + r.PayloadResponse);
}
catch (FaultException ex)
{
    Console.WriteLine(ex.Message);
}
 

FaultException is the base WCF Exception.

Combining Continuations, Generics, Extension Methods, and Task completion inspections; a developer can create a sort of funnel into the Service core and then out of the Service core.  A Continuation allows a developer to offload some internal Exception processing like logging to the Event log.

The complete code solution accompanies this article.

Conclusion

If TPL and WCF are to work well together; a developer must think about how to bridge their two Exception models.  Understanding how each model works is the key to bridging the two models.  WCF requires special Exception packaging to move an Exception over the wire to the client.  TPL AggregateExceptions require observation or handling.

Resources

Specifying and Handling Faults in Contracts and Services

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read