Create mobile apps with HTML5, JavaScript and Visual Studio

(Elle) #1

74 msdn magazine Async Programming


We can take the “no op” interception behavior shown in Figure 2


as a starting point.


Next, we add the code to detect the task-returning methods and


replace the returned Task with a new wrapper Task that logs the out-


come. 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.


Th e 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 aft er logging it. Note that this implementation doesn’t


change the original Task’s outcome, but a diff erent behavior could


replace or ignore the exceptions the original Task might introduce.


Dealing with Generics


Dealing with methods that return Task is a bit more complex,


particularly if you want to avoid taking a performance hit. Let’s


ignore for now the problem of fi guring out what “T” is and assume


it’s already known. As Figure 5 shows, we can write a generic method


that can handle Task for a known “T,” taking advantage of the


asynchronous language features available in C# 5.0.


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 refl ection, 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,


fi nally, invoking the delegate. Th is process can be quite expensive, so


it’s a good idea to cache these delegates. If the T is part of the meth-


od’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


benefi ts from the C# language features, as shown in Figure 6.


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 fi t the delegate approach just like the generic Task. Figure


7 shows the updated Invoke method.


All that’s left is implementing the GetWrapperCreator method.


Th is method will perform the expensive refl ection calls to create


the delegates and use a ConcurrentDictionary to cache them. Th ese


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. Th is is shown in Figure 8.


For the non-generic Task case, no refl ection is needed and the


existing non-generic method can be used as the desired delegate


as is. When dealing with Task, the necessary refl ection calls are


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;
}
}

Figure 4 Logging the Outcome


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;
}
}

Figure 5 A Generic Method to Handle Task


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;
}
}

Figure 6 Splitting the Delegate Creation 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;
}

Figure 7 The Updated Invoke Method

Free download pdf