目标
主要是想为服务方法注入公用的异常处理代码,从而使得业务代码简洁。本人使用Unity.Interception主键来达到这个目标。由于希望默认就执行拦截,所以使用了虚方法拦截器。要实现拦截,需要实现一个拦截处理类,此类型要求实现接口ICallHandler,例如:
public class ServiceHandler : ICallHandler { public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext) { Trace.WriteLine("开始调用"); IMethodReturn result; result = getNext()(input, getNext); if (result.Exception != null) { Trace.WriteLine("发生了异常"); } Trace.WriteLine("结束调用"); return result; } public int Order { get; set; } }
此外还定义了servicebase基类。
public class ServiceBase { }
该类型没有任何方法,这里添加一个继承与该类型的子类,同时添加一个方法(注意,由于使用虚方法拦截,需要拦截的方法必须标记为virtual)
public class FakeService : ServiceBase { public virtual int GetInt() { throw new Exception(""); return 100; } }
使用单元测试来调用这个方法,得到的结果:
以上是使用Unity在方法调用前后注入的例子,对于同步方法而言并不存在问题。由于.NET 4.5引入了async和await,异步方法变得常见,使用传统的方式注入变得行不通。其实,不仅仅是async方法,所有awaitable的方法都存在这个问题。
原因很简单,对于同步方法(不可等待的方法)而言,调用前后就是内部执行调用的前后,而对于返回Task类型的方法而言,调用结束后,异步操作并未结束,所以即使异步操作发生了异常,也无法被捕捉。这里使用一个异步方法进行实验:
由结果可见,拦截前后并没有发生异常,异常时在对Task对象等待的时候发生的。
方案
既然知道了为何无法拦截,那么就很容易得出方案:将拦截的范围延伸到Task方法执行完毕之后的点。首先,拿不带返回值的Task实验。对于Task而言,我们只关心Task结束后的操作,而我们又不需要为其返回一个对象。所以,我们一定可以使用一个参数签名为Action
public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext) { Trace.WriteLine("开始调用"); var result = getNext()(input, getNext); if (result.ReturnValue is Task) { var task = result.ReturnValue as Task; var continued = task.ContinueWith(t => { if (t.IsFaulted) { Trace.WriteLine("发生了异常"); } Trace.WriteLine("结束调用"); }); return input.CreateMethodReturn(continued); } if (result.Exception != null) { Trace.WriteLine("发生了异常"); } Trace.WriteLine("结束调用"); return result; }
对应的结果如下:
对比前后两次的结果,可以发现我们达成了目标。
困境
对于单纯的Task,可以使用单纯的ContinueWith来解决,然而对于带返回值的Task
事实上,理论上说,我们可以把输入参数限定为基类Task,从而调用Func
如果放弃这种通用的方式,我们还可以对Task
动态构造
上文中提到的“原始”方法的问题所在是要求枚举各种类型,我们知道是不可能的。那么动态构造的思路就是碰到新类型的时候,我们就”添加一个If“,即加上一种处理方式。这就很像是在运行期间写代码了,而解决这类问题,我们可以使用ExpressionTree。
也就是说,我们需要在运行时根据不同的情况调用不同重载的ContinueWith。这里分两步:一,找到合适的方法重载;二,构造合适的参数。
先找方法,对于Task
private MethodInfo FindContinueWith(Type taskType, bool hasReturn) { var methods = taskType.GetMethods().Where(i => i.Name == "ContinueWith").ToList(); if (hasReturn) { var returnType = taskType.GetGenericArguments().First(); return methods.Where(i => { var pars = i.GetParameters().ToList(); return pars.Count == 1 && pars.First().ParameterType.Name.StartsWith("F"); }).First().MakeGenericMethod(returnType); } return methods.Where(i => { var pars = i.GetParameters().ToList(); return pars.Count == 1 && pars.First().ParameterType.Name.StartsWith("A") && pars.First().ParameterType.IsGenericType; }) .First(); }
然后生成参数,这里先构造一个Expression:
private Expression MakeContinueExpression(Type taskType, Expression actionExp, bool hasReturn) { ParameterExpression taskParam; Expression handelTaskExp; if (!hasReturn) { taskParam = Expression.Parameter(typeof (Task)); //当Task不带返回值的时候,使用(t)=>action(t) handelTaskExp = Expression.Invoke(actionExp, taskParam); return Expression.Lambda(handelTaskExp, taskParam); } taskParam = Expression.Parameter(taskType); handelTaskExp = Expression.Invoke(actionExp, taskParam); var returnType = taskParam.Type.GetGenericArguments()[0]; var defaultResult = Expression.Default(returnType); var returnTarget = Expression.Label(returnType); var returnLable = Expression.Label(returnTarget, defaultResult); var paramResult = Expression.PropertyOrField(taskParam, "Result"); var returnExp = Expression.Return(returnTarget, paramResult); //当Task带返回值的时候,使用(t)=>{action(t);return t.Result;} var blockExp = Expression.Block(handelTaskExp, returnExp, returnLable); var expression = Expression.Lambda(blockExp, taskParam); return expression; }
参数中的Action代表的是我们需要额外做的事情,这样做的好处是,对于一个指定的Task
最后就是编译ExpressionTree生成一个委托:
private Func, object> MakeContinueTaskFactory(Type taskType, bool hasReturn) { var key = taskType.FullName; return ConcurrentDic.GetOrAdd(key, k => { var actionParam = Expression.Parameter(typeof (Action )); var continueParam = MakeContinueExpression(taskType, actionParam, hasReturn); var taskParam = Expression.Parameter(typeof (Task)); var taskTexp = Expression.Convert(taskParam, taskType); var mehtodInfo = FindContinueWith(taskType, hasReturn); var callExp = Expression.Call(taskTexp, mehtodInfo, continueParam); var lambda = Expression.Lambda , object>>(callExp, taskParam, actionParam); return lambda.Compile(); }); }
这个方法返回一个委托,该委托接受一个Task,和一个Action,执行后返回另外一个Task(ContinueWith)。
这里是完整的代码:
using System.Linq.Expressions.Caching; using System.Reflection; using System.Threading.Tasks; // ReSharper disable once CheckNamespace namespace System.Linq.Expressions { public class TaskInjector : CacheBlock<string, Func, object>> { /// /// 获取Task的ContinueWith方法 /// /// /// /// private MethodInfo FindContinueWith(Type taskType, bool hasReturn) { var methods = taskType.GetMethods().Where(i => i.Name == "ContinueWith").ToList(); if (hasReturn) { var returnType = taskType.GetGenericArguments().First(); return methods.Where(i => { var pars = i.GetParameters().ToList(); return pars.Count == 1 && pars.First().ParameterType.Name.StartsWith("F"); }).First().MakeGenericMethod(returnType); } return methods.Where(i => { var pars = i.GetParameters().ToList(); return pars.Count == 1 && pars.First().ParameterType.Name.StartsWith("A") && pars.First().ParameterType.IsGenericType; }) .First(); } /// /// 针对不同Task生成不同的ContinueWith委托 /// /// /// /// /// private Expression MakeContinueExpression(Type taskType, Expression actionExp, bool hasReturn) { ParameterExpression taskParam; Expression handelTaskExp; if (!hasReturn) { taskParam = Expression.Parameter(typeof (Task)); //当Task不带返回值的时候,使用(t)=>action(t) handelTaskExp = Expression.Invoke(actionExp, taskParam); return Expression.Lambda(handelTaskExp, taskParam); } taskParam = Expression.Parameter(taskType); handelTaskExp = Expression.Invoke(actionExp, taskParam); var returnType = taskParam.Type.GetGenericArguments()[0]; var defaultResult = Expression.Default(returnType); var returnTarget = Expression.Label(returnType); var returnLable = Expression.Label(returnTarget, defaultResult); var paramResult = Expression.PropertyOrField(taskParam, "Result"); var returnExp = Expression.Return(returnTarget, paramResult); //当Task带返回值的时候,使用(t)=>{action(t);return t.Result;} var blockExp = Expression.Block(handelTaskExp, returnExp, returnLable); var expression = Expression.Lambda(blockExp, taskParam); return expression; } /// /// 为指定的Task类型编译一个ContinueWith的生成器 /// /// /// /// private Func , object> MakeContinueTaskFactory(Type taskType, bool hasReturn) { var key = taskType.FullName; return ConcurrentDic.GetOrAdd(key, k => { var actionParam = Expression.Parameter(typeof (Action )); var continueParam = MakeContinueExpression(taskType, actionParam, hasReturn); var taskParam = Expression.Parameter(typeof (Task)); var taskTexp = Expression.Convert(taskParam, taskType); var mehtodInfo = FindContinueWith(taskType, hasReturn); var callExp = Expression.Call(taskTexp, mehtodInfo, continueParam); var lambda = Expression.Lambda , object>>(callExp, taskParam, actionParam); return lambda.Compile(); }); } /// /// 为Task类型的对象注入代码 /// /// /// /// public object Inject(Task task, Action action) { var runtimeType = task.GetType(); var hasReturn = runtimeType.IsGenericType && runtimeType.GetProperty("Result").PropertyType.Name != "VoidTaskResult"; var func = MakeContinueTaskFactory(runtimeType, hasReturn); return func(task, action); } public static TaskInjector Instance = new TaskInjector(); } }
以及一个辅助的缓存类:
using System.Linq.Expressions.Caching; using System.Reflection; using System.Threading.Tasks; // ReSharper disable once CheckNamespace namespace System.Linq.Expressions { public class TaskInjector : CacheBlock<string, Func, object>> { /// /// 获取Task的ContinueWith方法 /// /// /// /// private MethodInfo FindContinueWith(Type taskType, bool hasReturn) { var methods = taskType.GetMethods().Where(i => i.Name == "ContinueWith").ToList(); if (hasReturn) { var returnType = taskType.GetGenericArguments().First(); return methods.Where(i => { var pars = i.GetParameters().ToList(); return pars.Count == 1 && pars.First().ParameterType.Name.StartsWith("F"); }).First().MakeGenericMethod(returnType); } return methods.Where(i => { var pars = i.GetParameters().ToList(); return pars.Count == 1 && pars.First().ParameterType.Name.StartsWith("A") && pars.First().ParameterType.IsGenericType; }) .First(); } /// /// 针对不同Task生成不同的ContinueWith委托 /// /// /// /// /// private Expression MakeContinueExpression(Type taskType, Expression actionExp, bool hasReturn) { ParameterExpression taskParam; Expression handelTaskExp; if (!hasReturn) { taskParam = Expression.Parameter(typeof (Task)); //当Task不带返回值的时候,使用(t)=>action(t) handelTaskExp = Expression.Invoke(actionExp, taskParam); return Expression.Lambda(handelTaskExp, taskParam); } taskParam = Expression.Parameter(taskType); handelTaskExp = Expression.Invoke(actionExp, taskParam); var returnType = taskParam.Type.GetGenericArguments()[0]; var defaultResult = Expression.Default(returnType); var returnTarget = Expression.Label(returnType); var returnLable = Expression.Label(returnTarget, defaultResult); var paramResult = Expression.PropertyOrField(taskParam, "Result"); var returnExp = Expression.Return(returnTarget, paramResult); //当Task带返回值的时候,使用(t)=>{action(t);return t.Result;} var blockExp = Expression.Block(handelTaskExp, returnExp, returnLable); var expression = Expression.Lambda(blockExp, taskParam); return expression; } /// /// 为指定的Task类型编译一个ContinueWith的生成器 /// /// /// /// private Func , object> MakeContinueTaskFactory(Type taskType, bool hasReturn) { var key = taskType.FullName; return ConcurrentDic.GetOrAdd(key, k => { var actionParam = Expression.Parameter(typeof (Action )); var continueParam = MakeContinueExpression(taskType, actionParam, hasReturn); var taskParam = Expression.Parameter(typeof (Task)); var taskTexp = Expression.Convert(taskParam, taskType); var mehtodInfo = FindContinueWith(taskType, hasReturn); var callExp = Expression.Call(taskTexp, mehtodInfo, continueParam); var lambda = Expression.Lambda , object>>(callExp, taskParam, actionParam); return lambda.Compile(); }); } /// /// 为Task类型的对象注入代码 /// /// /// /// public object Inject(Task task, Action action) { var runtimeType = task.GetType(); var hasReturn = runtimeType.IsGenericType && runtimeType.GetProperty("Result").PropertyType.Name != "VoidTaskResult"; var func = MakeContinueTaskFactory(runtimeType, hasReturn); return func(task, action); } public static TaskInjector Instance = new TaskInjector(); } }
此时,我们就可以继续改造Handler实现:
public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext) { Trace.WriteLine("开始调用"); var result = getNext()(input, getNext); if (result.ReturnValue is Task) { var task = result.ReturnValue as Task; var continued = TaskInjector.Instance.Inject(task, (t) => { if (t.IsFaulted) { Trace.WriteLine("发生了异常"); } Trace.WriteLine("偷看值:" + PropertyFieldLoader.Instance.Load<object>(task, task.GetType(), "Result")); Trace.WriteLine("结束调用"); }); return input.CreateMethodReturn(continued); } if (result.Exception != null) { Trace.WriteLine("发生了异常"); } Trace.WriteLine("结束调用"); return result; }
对应的结果如下:
而对于一个返回Task
public virtual async Task<int> GetIntAsync() { return await Task.FromResult(1000); }
结果如下: