msdnmagazine.com February 2014 75
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.
Th is 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. Th e example in Figure 9 shows how a container
can be confi gured to intercept an object and use this new behavior,
and the resulting output when diff erent methods are invoked.
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 excep-
tion’s stack trace, refl ecting the way the exception was rethrown
when awaiting for the task. An alternative approach can use the
ContinueWith method and a TaskCompletionSource
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.
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 sup-
port asynchronous operations. Full source code for the example
is available at msdn.microsoft.com/magazine/msdnmag0214. Q
FERNANDO SIMONAZZI is a soft ware 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. Th ese days he drives the Microsoft Enterprise Library, Unity,
CQRS Journey and NUI patterns projects. Prior to that, he was a researcher
and soft ware 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 )
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;
}
});
}
Figure 8 Implementing the GetWrapperCreator Method
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
Figure 9 Confi guring a Container to Intercept an Object and
Use the New Behavior
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;
}
Figure 10 Using ContinueWith instead of the Await Keyword