February 2014

Volume 29 Number 2

Async Programming : Intercepting Asynchronous Methods Using Unity Interception

Fernando Simonazzi | February 2014

Unity (not to be confused with the Unity3D game engine) is a general-purpose, extensible, dependency injection container with interception support for use in any type of Microsoft .NET Framework-based application. Unity is designed and maintained by the Microsoft patterns & practices team. It can be easily added to your application via NuGet, and you’ll find the main hub of learning resources related to Unity at msdn.com/unity.

This article focuses on Unity interception. Interception is a technique that’s useful when you want to modify the behavior of individual objects without affecting the behavior of other objects from the same class, very much as you’d do when using the Decorator pattern (the Wikipedia definition of the Decorator pattern can be found here: bit.ly/1gZZUQu). Interception provides a flexible approach for adding new behaviors to an object at run time. These behaviors typically address some crosscutting concerns, such as logging or data validation. Interception is often used as the underlying mechanism for aspect-oriented programming (AOP). The runtime interception feature in Unity allows you to effectively intercept method calls to objects and perform pre- and post-processing of these calls.

Interception in the Unity container has two main components: interceptors and interception behaviors. Interceptors determine the mechanism used to intercept the calls to methods in the intercepted object, while the interception behaviors determine the actions that are performed on the intercepted method calls. An intercepted object is supplied with a pipeline of interception behaviors. When a method call is intercepted, each behavior in the pipeline is allowed to inspect and even modify the parameters of the method call, and eventually the original method implementation is invoked. Upon return, each behavior can inspect or replace the values returned or exceptions thrown by the original implementation or the previous behavior in the pipeline. Finally, the original caller gets the resulting return value, if any, or the resulting exception. Figure 1 depicts the interception mechanism.

Unity Interception Mechanism
Figure 1 Unity Interception Mechanism

There are two types of interception techniques: instance interception and type interception. With instance interception, Unity dynamically creates a proxy object that’s inserted between the client and the target object. The proxy object is then responsible for passing the calls made by the client to the target object through the behaviors. You can use Unity instance interception to intercept objects created both by the Unity container and outside of the container, and you can use it to intercept both virtual and non-virtual methods. However, you can’t cast the dynamically created proxy type to the type of the target object. With type interception, Unity dynamically creates a new type that derives from the type of the target object and that includes the behaviors that handle the crosscutting concerns. The Unity container instantiates objects of the derived type at run time. Instance interception can only intercept public instance methods. Type interception can intercept both public and protected virtual methods. Keep in mind that, due to platform constraints, Unity interception does not support Windows Phone and Windows Store app development, though the core Unity container does.

For a primer on Unity, see “Dependency Injection with Unity” (Microsoft patterns & practices, 2013) at amzn.to/16rfy0B. For more information about interception in the Unity container, see the MSDN Library article, “Interception using Unity,” at bit.ly/1cWCnwM.

Intercepting Task-Based Asynchronous Pattern (TAP) Asynchronous Methods

The interception mechanism is simple enough, but what happens if the intercepted method represents an asynchronous operation that returns a Task object? In a way, nothing really changes: A method is invoked and returns a value (the Task object) or throws an exception, so it can be intercepted just like any other method. But you’re probably interested in dealing with the actual outcome of the asynchronous operation rather than the Task representing it. For example, you might want to log the Task’s return value, or handle any exception the Task might produce.

Fortunately, having an actual object representing the outcome of the operation makes interception of this asynchronous pattern relatively simple. Other asynchronous patterns are quite a bit more difficult to intercept: In the Asynchronous Programming Model (bit.ly/ICl8aH) two methods represent a single asynchronous operation, while in the Event-based Asynchronous Pattern (bit.ly/19VdUWu) asynchronous operations are represented by a method to initiate the operation and an associated event to signal its completion.

In order to accomplish the interception of the asynchronous TAP operation, you can replace the Task returned by the method with a new Task that performs the necessary post-processing after the original Task completes. Callers of the intercepted method will receive the new Task matching the method’s signature, and will observe the result of the intercepted method’s implementation, plus whatever extra processing the interception behavior performs.

We’ll develop a sample implementation of the basic approach to intercept TAP asynchronous operations in which we want to log the completion of asynchronous operations. You can adapt this sample to create your own behaviors that can intercept asynchronous operations.

Simple Case

Let’s start with a simple case: intercepting asynchronous methods that return a non-generic Task. We need to be able to detect that the intercepted method returns a Task and replace that Task with a new one that performs the appropriate logging.

We can take the “no op” interception behavior shown in Figure 2 as a starting point.

Figure 2 Simple Interception

public class LoggingAsynchronousOperationInterceptionBehavior 
  : IInterceptionBehavior
{
  public IMethodReturn Invoke(IMethodInvocation input,
    GetNextInterceptionBehaviorDelegate getNext)
  {
    // Execute the rest of the pipeline and get the return value
    IMethodReturn value = getNext()(input, getNext);
    return value;
  }
  #region additional interception behavior methods
  public IEnumerable<Type> GetRequiredInterfaces()
  {
    return Type.EmptyTypes;
  }
  public bool WillExecute
  {
    get { return true; }
  }
  #endregion
}

Next, we add the code to detect the task-returning methods and replace the returned Task with a new wrapper Task that logs the outcome. To accomplish this, the CreateMethodReturn on the input object is called to create a new IMethodReturn object representing a wrapper Task created by the new CreateWrapperTask method in the behavior, as shown in Figure 3.

Figure 3 Returning a Task

public IMethodReturn Invoke(IMethodInvocation input,
  GetNextInterceptionBehaviorDelegate getNext)
{
  // Execute the rest of the pipeline and get the return value
  IMethodReturn value = getNext()(input, getNext);
  // Deal with tasks, if needed
  var method = input.MethodBase as MethodInfo;
  if (value.ReturnValue != null
    && method != null
    && typeof(Task) == method.ReturnType)
  {
    // If this method returns a Task, override the original return value
    var task = (Task)value.ReturnValue;
    return input.CreateMethodReturn(this.CreateWrapperTask(task, input),
      value.Outputs);
  }
  return value;
}

The new CreateWrapperTask method returns a Task that waits for the original Task to complete and logs its outcome, as shown in Figure 4. If the task resulted in an exception, the method will rethrow it after logging it. Note that this implementation doesn’t change the original Task’s outcome, but a different behavior could replace or ignore the exceptions the original Task might introduce.

Figure 4 Logging the Outcome

private async Task CreateWrapperTask(Task task,
  IMethodInvocation input)
{
  try
  {
    await task.ConfigureAwait(false);
    Trace.TraceInformation("Successfully finished async operation {0}",
      input.MethodBase.Name);
  }
  catch (Exception e)
  {
    Trace.TraceWarning("Async operation {0} threw: {1}",
      input.MethodBase.Name, e);
    throw;
  }
}

Dealing with Generics

Dealing with methods that return Task<T> is a bit more complex, particularly if you want to avoid taking a performance hit. Let’s ignore for now the problem of figuring out what “T” is and assume it’s already known. As Figure 5 shows, we can write a generic method that can handle Task<T> for a known “T,” taking advantage of the asynchronous language features available in C# 5.0.

Figure 5 A Generic Method to Handle Task<T>

private async Task<T> CreateGenericWrapperTask<T>(Task<T> task,
  IMethodInvocation input)
{
  try
  {
    T value = await task.ConfigureAwait(false);
    Trace.TraceInformation("Successfully finished async operation {0} with value: {1}",
      input.MethodBase.Name, value);
    return value;
  }
  catch (Exception e)
  {
    Trace.TraceWarning("Async operation {0} threw: {1}", input.MethodBase.Name, e);
    throw;
  }
}

As with the simple case, the method just logs without changing the original behavior. But because the wrapped Task now returns a value, the behavior could also replace this value, if needed.

 How can we invoke this method to obtain the replacement Task? We need to resort to reflection, extracting the T from the intercepted method’s generic return type, creating a closed version of this generic method for that T and creating a delegate out of it and, finally, invoking the delegate. This process can be quite expensive, so it’s a good idea to cache these delegates. If the T is part of the method’s signature, we wouldn’t be able to create a delegate of a method and invoke it without knowing the T, so we’ll split our earlier method into two methods: one with the desired signature, and one that benefits from the C# language features, as shown in Figure 6.

Figure 6 Splitting the Delegate Creation Method

private Task CreateGenericWrapperTask<T>(Task task, IMethodInvocation input)
{
  return this.DoCreateGenericWrapperTask<T>((Task<T>)task, input);
}
private async Task<T> DoCreateGenericWrapperTask<T>(Task<T> task,
  IMethodInvocation input)
{
  try
  {
    T value = await task.ConfigureAwait(false);
    Trace.TraceInformation("Successfully finished async operation {0} with value: {1}",
      input.MethodBase.Name, value);
    return value;
  }
  catch (Exception e)
  {
    Trace.TraceWarning("Async operation {0} threw: {1}", input.MethodBase.Name, e);
    throw;
  }
}

Next, we change the interception method so we use the correct delegate to wrap the original task, which we get by invoking the new GetWrapperCreator method and passing the expected task type. We don’t need a special case for the non-generic Task, because it can fit the delegate approach just like the generic Task<T>. Figure 7 shows the updated Invoke method.

Figure 7 The Updated Invoke Method

public IMethodReturn Invoke(IMethodInvocation input,
  GetNextInterceptionBehaviorDelegate getNext)
{
  IMethodReturn value = getNext()(input, getNext);
  var method = input.MethodBase as MethodInfo;
  if (value.ReturnValue != null
    && method != null
    && typeof(Task).IsAssignableFrom(method.ReturnType))
  {
    // If this method returns a Task, override the original return value
    var task = (Task)value.ReturnValue;
    return input.CreateMethodReturn(
      this.GetWrapperCreator(method.ReturnType)(task, input), value.Outputs);
  }
  return value;
}

All that’s left is implementing the GetWrapperCreator method. This method will perform the expensive reflection calls to create the delegates and use a ConcurrentDictionary to cache them. These wrapper creator delegates are of type Func<Task, IMethodInvocation, Task>; we want to get the original task and the IMethodInvocation object representing the call to the invocation to the asynchronous method and return a wrapper Task. This is shown in Figure 8.

Figure 8 Implementing the GetWrapperCreator Method

private readonly ConcurrentDictionary<Type, Func<Task, IMethodInvocation, Task>>
  wrapperCreators = new ConcurrentDictionary<Type, Func<Task,
  IMethodInvocation, Task>>();
private Func<Task, IMethodInvocation, Task> GetWrapperCreator(Type taskType)
{
  return this.wrapperCreators.GetOrAdd(
    taskType,
    (Type t) =>
    {
      if (t == typeof(Task))
      {
        return this.CreateWrapperTask;
      }
      else if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Task<>))
      {
        return (Func<Task, IMethodInvocation, Task>)this.GetType()
          .GetMethod("CreateGenericWrapperTask",
             BindingFlags.Instance | BindingFlags.NonPublic)
          .MakeGenericMethod(new Type[] { t.GenericTypeArguments[0] })
          .CreateDelegate(typeof(Func<Task, IMethodInvocation, Task>), this);
      }
      else
      {
        // Other cases are not supported
        return (task, _) => task;
      }
    });
}

For the non-generic Task case, no reflection is needed and the existing non-generic method can be used as the desired delegate as is. When dealing with Task<T>, the necessary reflection calls are performed to create the corresponding delegate. Finally, we can’t support any other Task type, as we wouldn’t know how to create it, so a no-op delegate that just returns the original task is returned.

This behavior can now be used on an intercepted object and will log the results of the tasks returned by the intercepted object’s methods for the cases where a value is returned and where an exception is thrown. The example in Figure 9 shows how a container can be configured to intercept an object and use this new behavior, and the resulting output when different methods are invoked.

Figure 9 Configuring a Container to Intercept an Object and Use the New Behavior

using (var container = new UnityContainer())
{
  container.AddNewExtension<Interception>();
  container.RegisterType<ITestObject, TestObject>(
    new Interceptor<InterfaceInterceptor>(),
    new InterceptionBehavior<LoggingAsynchronousOperationInterceptionBehavior>());
  var instance = container.Resolve<ITestObject>();
  await instance.DoStuffAsync("test");
  // Do some other work
}
Output:
vstest.executionengine.x86.exe Information: 0 : ­
  Successfully finished async operation ­DoStuffAsync with value: test
vstest.executionengine.x86.exe Warning: 0 : ­
  Async operation DoStuffAsync threw: ­
    System.InvalidOperationException: invalid
   at AsyncInterception.Tests.AsyncBehaviorTests2.TestObject.<­
     DoStuffAsync>d__38.MoveNext() in d:\dev\interceptiontask\­
       AsyncInterception\­AsyncInterception.Tests\­
         AsyncBehaviorTests2.cs:line 501
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(­Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.­
     HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
   at AsyncInterception.LoggingAsynchronousOperationInterceptionBehavior.<­
     CreateWrapperTask>d__3.MoveNext() in d:\dev\interceptiontask\­
       AsyncInterception\AsyncInterception\­
         LoggingAsynchronousOperationInterceptionBehavior.cs:line 63

Covering Our Tracks

As you can see in the resulting output in Figure 9, the approach used in this implementation results in a light change in the exception’s stack trace, reflecting the way the exception was rethrown when awaiting for the task. An alternative approach can use the ContinueWith method and a TaskCompletionSource<T> instead of the await keyword to avoid this issue, at the expense of having a more complex (and potentially more expensive) implementation such as what’s shown in Figure 10.

Figure 10 Using ContinueWith instead of the Await Keyword

private Task CreateWrapperTask(Task task, IMethodInvocation input)
{
  var tcs = new TaskCompletionSource<bool>();
  task.ContinueWith(
    t =>
    {
      if (t.IsFaulted)
      {
        var e = t.Exception.InnerException;
        Trace.TraceWarning("Async operation {0} threw: {1}",
          input.MethodBase.Name, e);
        tcs.SetException(e);
      }
      else if (t.IsCanceled)
      {
        tcs.SetCanceled();
      }
      else
      {
        Trace.TraceInformation("Successfully finished async operation {0}",
          input.MethodBase.Name);
        tcs.SetResult(true);
      }
    },
    TaskContinuationOptions.ExecuteSynchronously);
  return tcs.Task;
}

Wrapping Up

We discussed several strategies for intercepting asynchronous methods and demonstrated them on an example that logs the completion of asynchronous operations. You can adapt this sample to create your own intercepting behaviors that would support asynchronous operations. Full source code for the example is available at msdn.microsoft.com/magazine/msdnmag0214.


Fernando Simonazzi is a software developer and architect with more than 15 years of professional experience. He has been a contributor to Microsoft patterns & practices projects, including several releases of the Enterprise Library, Unity, CQRS Journey and Prism. Simonazzi is also an associate at Clarius Consulting.

Dr. Grigori Melnik is a principal program manager on the Microsoft patterns & practices team. These days he drives the Microsoft Enterprise Library, Unity, CQRS Journey and NUI patterns projects. Prior to that, he was a researcher and software engineer long enough ago to remember the joy of programming in Fortran. Dr. Melnik blogs at blogs.msdn.com/agile.

Thanks to the following technical expert for reviewing this article: Stephen Toub (Microsoft)