目标

主要是想为服务方法注入公用的异常处理代码,从而使得业务代码简洁。本人使用Unity.Interception主键来达到这个目标。由于希望默认就执行拦截,所以使用了虚方法拦截器。要实现拦截,需要实现一个拦截处理类,此类型要求实现接口ICallHandler,例如:

  1. public class ServiceHandler : ICallHandler
  2. {
  3. public IMethodReturn Invoke(IMethodInvocation input,
  4. GetNextHandlerDelegate getNext)
  5. {
  6. Trace.WriteLine("开始调用");
  7. IMethodReturn result;
  8. result = getNext()(input, getNext);
  9. if (result.Exception != null)
  10. {
  11. Trace.WriteLine("发生了异常");
  12. }
  13. Trace.WriteLine("结束调用");
  14. return result;
  15. }
  16.  
  17. public int Order { get; set; }
  18. }

此外还定义了servicebase基类。

  1. public class ServiceBase
  2. {
  3. }

该类型没有任何方法,这里添加一个继承与该类型的子类,同时添加一个方法(注意,由于使用虚方法拦截,需要拦截的方法必须标记为virtual)

  1. public class FakeService : ServiceBase
  2. {
  3. public virtual int GetInt()
  4. {
  5. throw new Exception("");
  6. return ;
  7. }
  8. }

使用单元测试来调用这个方法,得到的结果:

以上是使用Unity在方法调用前后注入的例子,对于同步方法而言并不存在问题。由于.NET 4.5引入了async和await,异步方法变得常见,使用传统的方式注入变得行不通。其实,不仅仅是async方法,所有awaitable的方法都存在这个问题。

原因很简单,对于同步方法(不可等待的方法)而言,调用前后就是内部执行调用的前后,而对于返回Task类型的方法而言,调用结束后,异步操作并未结束,所以即使异步操作发生了异常,也无法被捕捉。这里使用一个异步方法进行实验:

由结果可见,拦截前后并没有发生异常,异常时在对Task对象等待的时候发生的。

方案

既然知道了为何无法拦截,那么就很容易得出方案:将拦截的范围延伸到Task方法执行完毕之后的点。首先,拿不带返回值的Task实验。对于Task而言,我们只关心Task结束后的操作,而我们又不需要为其返回一个对象。所以,我们一定可以使用一个参数签名为Action<Task>的ContinueWith来达到目标,修改Handler的实现如下:

  1. public IMethodReturn Invoke(IMethodInvocation input,
  2. GetNextHandlerDelegate getNext)
  3. {
  4. Trace.WriteLine("开始调用");
  5. var result = getNext()(input, getNext);
  6.  
  7. if (result.ReturnValue is Task)
  8. {
  9. var task = result.ReturnValue as Task;
  10. var continued = task.ContinueWith(t =>
  11. {
  12. if (t.IsFaulted)
  13. {
  14. Trace.WriteLine("发生了异常");
  15. }
  16. Trace.WriteLine("结束调用");
  17. });
  18. return input.CreateMethodReturn(continued);
  19. }
  20.  
  21. if (result.Exception != null)
  22. {
  23. Trace.WriteLine("发生了异常");
  24. }
  25. Trace.WriteLine("结束调用");
  26. return result;
  27. }

对应的结果如下:

对比前后两次的结果,可以发现我们达成了目标。

困境

对于单纯的Task,可以使用单纯的ContinueWith来解决,然而对于带返回值的Task<T>,就没那么简单了。这是因为我们要保证ContinueWith之后,返回的Task的类型和目标方法的返回值类型一直,例如,如果方法要求返回一个Task<int>,那么,我们调用的ContinueWith方法必然是要求参数类型为Func<Task<int>,int>的重载。

事实上,理论上说,我们可以把输入参数限定为基类Task,从而调用Func<Task,dynamic>这个类型重载,然后通过动态类型适配返回值...然而通过这种方式调用的话,ContinueWith返回的对象类型为ContinuedXXXTask,一个运行时类型,是无法和方法签名上的类型匹配的。

如果放弃这种通用的方式,我们还可以对Task<T>的泛型参数T进行判断,然后转型,然后再调用ContinueWith,不过这样的话只能处理已知类型。然而这种最原始的方式给了我们思路:动态构造。

动态构造

上文中提到的“原始”方法的问题所在是要求枚举各种类型,我们知道是不可能的。那么动态构造的思路就是碰到新类型的时候,我们就”添加一个If“,即加上一种处理方式。这就很像是在运行期间写代码了,而解决这类问题,我们可以使用ExpressionTree。

也就是说,我们需要在运行时根据不同的情况调用不同重载的ContinueWith。这里分两步:一,找到合适的方法重载;二,构造合适的参数。

先找方法,对于Task<T>我们需要调用的参数为Func<,>的重载,对于Task则是Action<>:

  1. private MethodInfo FindContinueWith(Type taskType, bool hasReturn)
  2. {
  3. var methods = taskType.GetMethods().Where(i => i.Name == "ContinueWith").ToList();
  4. if (hasReturn)
  5. {
  6. var returnType = taskType.GetGenericArguments().First();
  7. return methods.Where(i =>
  8. {
  9. var pars = i.GetParameters().ToList();
  10. return pars.Count == && pars.First().ParameterType.Name.StartsWith("F");
  11. }).First().MakeGenericMethod(returnType);
  12. }
  13. return methods.Where(i =>
  14. {
  15. var pars = i.GetParameters().ToList();
  16. return pars.Count ==
  17. && pars.First().ParameterType.Name.StartsWith("A")
  18. && pars.First().ParameterType.IsGenericType;
  19. })
  20. .First();
  21. }

然后生成参数,这里先构造一个Expression:

  1. private Expression MakeContinueExpression(Type taskType, Expression actionExp, bool hasReturn)
  2. {
  3. ParameterExpression taskParam;
  4. Expression handelTaskExp;
  5.  
  6. if (!hasReturn)
  7. {
  8. taskParam = Expression.Parameter(typeof (Task));
  9. //当Task不带返回值的时候,使用(t)=>action(t)
  10. handelTaskExp = Expression.Invoke(actionExp, taskParam);
  11. return Expression.Lambda(handelTaskExp, taskParam);
  12. }
  13.  
  14. taskParam = Expression.Parameter(taskType);
  15. handelTaskExp = Expression.Invoke(actionExp, taskParam);
  16.  
  17. var returnType = taskParam.Type.GetGenericArguments()[];
  18. var defaultResult = Expression.Default(returnType);
  19. var returnTarget = Expression.Label(returnType);
  20. var returnLable = Expression.Label(returnTarget, defaultResult);
  21. var paramResult = Expression.PropertyOrField(taskParam, "Result");
  22. var returnExp = Expression.Return(returnTarget, paramResult);
  23. //当Task带返回值的时候,使用(t)=>{action(t);return t.Result;}
  24. var blockExp = Expression.Block(handelTaskExp, returnExp, returnLable);
  25. var expression = Expression.Lambda(blockExp, taskParam);
  26. return expression;
  27. }

参数中的Action代表的是我们需要额外做的事情,这样做的好处是,对于一个指定的Task<T>,无论你想额外做什么时候,只需要编译一次ExpressionTree,这有利于ExpressionTree缓存从而提高性能。

最后就是编译ExpressionTree生成一个委托:

  1. private Func<Task, Action<Task>, object> MakeContinueTaskFactory(Type taskType, bool hasReturn)
  2. {
  3. var key = taskType.FullName;
  4. return ConcurrentDic.GetOrAdd(key, k =>
  5. {
  6. var actionParam = Expression.Parameter(typeof (Action<Task>));
  7. var continueParam = MakeContinueExpression(taskType, actionParam, hasReturn);
  8. var taskParam = Expression.Parameter(typeof (Task));
  9. var taskTexp = Expression.Convert(taskParam, taskType);
  10. var mehtodInfo = FindContinueWith(taskType, hasReturn);
  11. var callExp = Expression.Call(taskTexp, mehtodInfo, continueParam);
  12. var lambda = Expression.Lambda<Func<Task, Action<Task>, object>>(callExp, taskParam, actionParam);
  13. return lambda.Compile();
  14. });
  15. }

这个方法返回一个委托,该委托接受一个Task,和一个Action,执行后返回另外一个Task(ContinueWith)。

这里是完整的代码:

  1. using System.Linq.Expressions.Caching;
  2. using System.Reflection;
  3. using System.Threading.Tasks;
  4.  
  5. // ReSharper disable once CheckNamespace
  6. namespace System.Linq.Expressions
  7. {
  8. public class TaskInjector : CacheBlock<string, Func<Task, Action<Task>, object>>
  9. {
  10. /// <summary>
  11. /// 获取Task的ContinueWith方法
  12. /// </summary>
  13. /// <param name="taskType"></param>
  14. /// <param name="hasReturn"></param>
  15. /// <returns></returns>
  16. private MethodInfo FindContinueWith(Type taskType, bool hasReturn)
  17. {
  18. var methods = taskType.GetMethods().Where(i => i.Name == "ContinueWith").ToList();
  19. if (hasReturn)
  20. {
  21. var returnType = taskType.GetGenericArguments().First();
  22. return methods.Where(i =>
  23. {
  24. var pars = i.GetParameters().ToList();
  25. return pars.Count == && pars.First().ParameterType.Name.StartsWith("F");
  26. }).First().MakeGenericMethod(returnType);
  27. }
  28. return methods.Where(i =>
  29. {
  30. var pars = i.GetParameters().ToList();
  31. return pars.Count ==
  32. && pars.First().ParameterType.Name.StartsWith("A")
  33. && pars.First().ParameterType.IsGenericType;
  34. })
  35. .First();
  36. }
  37.  
  38. /// <summary>
  39. /// 针对不同Task生成不同的ContinueWith委托
  40. /// </summary>
  41. /// <param name="taskType"></param>
  42. /// <param name="actionExp"></param>
  43. /// <param name="hasReturn"></param>
  44. /// <returns></returns>
  45. private Expression MakeContinueExpression(Type taskType, Expression actionExp, bool hasReturn)
  46. {
  47. ParameterExpression taskParam;
  48. Expression handelTaskExp;
  49.  
  50. if (!hasReturn)
  51. {
  52. taskParam = Expression.Parameter(typeof (Task));
  53. //当Task不带返回值的时候,使用(t)=>action(t)
  54. handelTaskExp = Expression.Invoke(actionExp, taskParam);
  55. return Expression.Lambda(handelTaskExp, taskParam);
  56. }
  57.  
  58. taskParam = Expression.Parameter(taskType);
  59. handelTaskExp = Expression.Invoke(actionExp, taskParam);
  60.  
  61. var returnType = taskParam.Type.GetGenericArguments()[];
  62. var defaultResult = Expression.Default(returnType);
  63. var returnTarget = Expression.Label(returnType);
  64. var returnLable = Expression.Label(returnTarget, defaultResult);
  65. var paramResult = Expression.PropertyOrField(taskParam, "Result");
  66. var returnExp = Expression.Return(returnTarget, paramResult);
  67. //当Task带返回值的时候,使用(t)=>{action(t);return t.Result;}
  68. var blockExp = Expression.Block(handelTaskExp, returnExp, returnLable);
  69. var expression = Expression.Lambda(blockExp, taskParam);
  70. return expression;
  71. }
  72.  
  73. /// <summary>
  74. /// 为指定的Task类型编译一个ContinueWith的生成器
  75. /// </summary>
  76. /// <param name="taskType"></param>
  77. /// <param name="hasReturn"></param>
  78. /// <returns></returns>
  79. private Func<Task, Action<Task>, object> MakeContinueTaskFactory(Type taskType, bool hasReturn)
  80. {
  81. var key = taskType.FullName;
  82. return ConcurrentDic.GetOrAdd(key, k =>
  83. {
  84. var actionParam = Expression.Parameter(typeof (Action<Task>));
  85. var continueParam = MakeContinueExpression(taskType, actionParam, hasReturn);
  86. var taskParam = Expression.Parameter(typeof (Task));
  87. var taskTexp = Expression.Convert(taskParam, taskType);
  88. var mehtodInfo = FindContinueWith(taskType, hasReturn);
  89. var callExp = Expression.Call(taskTexp, mehtodInfo, continueParam);
  90. var lambda = Expression.Lambda<Func<Task, Action<Task>, object>>(callExp, taskParam, actionParam);
  91. return lambda.Compile();
  92. });
  93. }
  94.  
  95. /// <summary>
  96. /// 为Task类型的对象注入代码
  97. /// </summary>
  98. /// <param name="task"></param>
  99. /// <param name="action"></param>
  100. /// <returns></returns>
  101. public object Inject(Task task, Action<Task> action)
  102. {
  103. var runtimeType = task.GetType();
  104. var hasReturn = runtimeType.IsGenericType && runtimeType.GetProperty("Result").PropertyType.Name != "VoidTaskResult";
  105. var func = MakeContinueTaskFactory(runtimeType, hasReturn);
  106. return func(task, action);
  107. }
  108.  
  109. public static TaskInjector Instance = new TaskInjector();
  110. }
  111. }

以及一个辅助的缓存类:

  1. using System.Linq.Expressions.Caching;
  2. using System.Reflection;
  3. using System.Threading.Tasks;
  4.  
  5. // ReSharper disable once CheckNamespace
  6. namespace System.Linq.Expressions
  7. {
  8. public class TaskInjector : CacheBlock<string, Func<Task, Action<Task>, object>>
  9. {
  10. /// <summary>
  11. /// 获取Task的ContinueWith方法
  12. /// </summary>
  13. /// <param name="taskType"></param>
  14. /// <param name="hasReturn"></param>
  15. /// <returns></returns>
  16. private MethodInfo FindContinueWith(Type taskType, bool hasReturn)
  17. {
  18. var methods = taskType.GetMethods().Where(i => i.Name == "ContinueWith").ToList();
  19. if (hasReturn)
  20. {
  21. var returnType = taskType.GetGenericArguments().First();
  22. return methods.Where(i =>
  23. {
  24. var pars = i.GetParameters().ToList();
  25. return pars.Count == && pars.First().ParameterType.Name.StartsWith("F");
  26. }).First().MakeGenericMethod(returnType);
  27. }
  28. return methods.Where(i =>
  29. {
  30. var pars = i.GetParameters().ToList();
  31. return pars.Count ==
  32. && pars.First().ParameterType.Name.StartsWith("A")
  33. && pars.First().ParameterType.IsGenericType;
  34. })
  35. .First();
  36. }
  37.  
  38. /// <summary>
  39. /// 针对不同Task生成不同的ContinueWith委托
  40. /// </summary>
  41. /// <param name="taskType"></param>
  42. /// <param name="actionExp"></param>
  43. /// <param name="hasReturn"></param>
  44. /// <returns></returns>
  45. private Expression MakeContinueExpression(Type taskType, Expression actionExp, bool hasReturn)
  46. {
  47. ParameterExpression taskParam;
  48. Expression handelTaskExp;
  49.  
  50. if (!hasReturn)
  51. {
  52. taskParam = Expression.Parameter(typeof (Task));
  53. //当Task不带返回值的时候,使用(t)=>action(t)
  54. handelTaskExp = Expression.Invoke(actionExp, taskParam);
  55. return Expression.Lambda(handelTaskExp, taskParam);
  56. }
  57.  
  58. taskParam = Expression.Parameter(taskType);
  59. handelTaskExp = Expression.Invoke(actionExp, taskParam);
  60.  
  61. var returnType = taskParam.Type.GetGenericArguments()[];
  62. var defaultResult = Expression.Default(returnType);
  63. var returnTarget = Expression.Label(returnType);
  64. var returnLable = Expression.Label(returnTarget, defaultResult);
  65. var paramResult = Expression.PropertyOrField(taskParam, "Result");
  66. var returnExp = Expression.Return(returnTarget, paramResult);
  67. //当Task带返回值的时候,使用(t)=>{action(t);return t.Result;}
  68. var blockExp = Expression.Block(handelTaskExp, returnExp, returnLable);
  69. var expression = Expression.Lambda(blockExp, taskParam);
  70. return expression;
  71. }
  72.  
  73. /// <summary>
  74. /// 为指定的Task类型编译一个ContinueWith的生成器
  75. /// </summary>
  76. /// <param name="taskType"></param>
  77. /// <param name="hasReturn"></param>
  78. /// <returns></returns>
  79. private Func<Task, Action<Task>, object> MakeContinueTaskFactory(Type taskType, bool hasReturn)
  80. {
  81. var key = taskType.FullName;
  82. return ConcurrentDic.GetOrAdd(key, k =>
  83. {
  84. var actionParam = Expression.Parameter(typeof (Action<Task>));
  85. var continueParam = MakeContinueExpression(taskType, actionParam, hasReturn);
  86. var taskParam = Expression.Parameter(typeof (Task));
  87. var taskTexp = Expression.Convert(taskParam, taskType);
  88. var mehtodInfo = FindContinueWith(taskType, hasReturn);
  89. var callExp = Expression.Call(taskTexp, mehtodInfo, continueParam);
  90. var lambda = Expression.Lambda<Func<Task, Action<Task>, object>>(callExp, taskParam, actionParam);
  91. return lambda.Compile();
  92. });
  93. }
  94.  
  95. /// <summary>
  96. /// 为Task类型的对象注入代码
  97. /// </summary>
  98. /// <param name="task"></param>
  99. /// <param name="action"></param>
  100. /// <returns></returns>
  101. public object Inject(Task task, Action<Task> action)
  102. {
  103. var runtimeType = task.GetType();
  104. var hasReturn = runtimeType.IsGenericType && runtimeType.GetProperty("Result").PropertyType.Name != "VoidTaskResult";
  105. var func = MakeContinueTaskFactory(runtimeType, hasReturn);
  106. return func(task, action);
  107. }
  108.  
  109. public static TaskInjector Instance = new TaskInjector();
  110. }
  111. }

此时,我们就可以继续改造Handler实现:

  1. public IMethodReturn Invoke(IMethodInvocation input,
  2. GetNextHandlerDelegate getNext)
  3. {
  4. Trace.WriteLine("开始调用");
  5. var result = getNext()(input, getNext);
  6.  
  7. if (result.ReturnValue is Task)
  8. {
  9. var task = result.ReturnValue as Task;
  10. var continued = TaskInjector.Instance.Inject(task, (t) =>
  11. {
  12. if (t.IsFaulted)
  13. {
  14. Trace.WriteLine("发生了异常");
  15. }
  16. Trace.WriteLine("偷看值:" + PropertyFieldLoader.Instance.Load<object>(task, task.GetType(), "Result"));
  17. Trace.WriteLine("结束调用");
  18. });
  19. return input.CreateMethodReturn(continued);
  20. }
  21.  
  22. if (result.Exception != null)
  23. {
  24. Trace.WriteLine("发生了异常");
  25. }
  26. Trace.WriteLine("结束调用");
  27. return result;
  28. }

对应的结果如下:

而对于一个返回Task<T>的方法:

  1. public virtual async Task<int> GetIntAsync()
  2. {
  3. return await Task.FromResult();
  4. }

结果如下:

使用Unity拦截一个返回Task的方法的更多相关文章

  1. .NET(C#):await返回Task的async方法

    众所周知,async方法只可以返回void,Task和Task<T>. 对于返回void的async方法,它并不是awaitable,所以其他方法不能用await方法来调用它,而返回Tas ...

  2. Html5 监听拦截Android返回键方法详解

    浏览器窗口有一个history对象,用来保存浏览历史. 如果当前窗口先后访问了三个网址,那么history对象就包括三项,history.length属性等于3. history对象提供了一系列方法, ...

  3. jquery ajax中支持哪些返回类型以及js中判断一个类型常用的方法?

    1 jquery ajax中支持哪些返回类型在JQuery中,AJAX有三种实现方式:$.ajax() , $.post , $.get(). 预期服务器返回的数据类型.如果不指定,jQuery 将自 ...

  4. 求一个整型数字中有没有相同的部分,例如12386123这个整型数字中相同的部分是123,相同的部分至少应该是2位数,如果有相同部分返回1,如果没有则返回0。方法是先将整型数字转换到数组中,再判断。函数为 int same(int num)其中num是输入的整型数字

    import java.util.ArrayList; import java.util.List; import java.util.Scanner; public class Test { pub ...

  5. 058、Java中定义一个没有参数没有返回值的方法

    01.代码如下: package TIANPAN; /** * 此处为文档注释 * * @author 田攀 微信382477247 */ public class TestDemo { public ...

  6. 在finally中调用一个需要await的方法

    最近在把code改写成async+await的形式,发现有些情况下需要在finally中需要调用异步方法,但是编译器不允许在cache和finally中出现await关键字...但是用Wait()或者 ...

  7. .Net4.0如何实现.NET4.5中的Task.Run及Task.Delay方法

    前言 .NET4.0下是没有Task.Run及Task.Delay方法的,而.NET4.5已经实现,对于还在使用.NET4.0的同学来说,如何在.NET4.0下实现这两个方法呢? 在.NET4.0下, ...

  8. struts2拦截器interceptor的配置方法及使用

    转: struts2拦截器interceptor的配置方法及使用 (2015-11-09 10:22:28) 转载▼ 标签: it 365 分类: Struts2  NormalText Code  ...

  9. C#处理MySql多个返回集的方法

    本文实例讲述了C#处理MySql多个返回集的方法.分享给大家供大家参考.具体方法如下: 关于Mysql返回多个集java和Php的较多,但是C#的完整代码好像没见过,研究了一下做个封装以后用 做一个M ...

随机推荐

  1. Spring(3.2.3) - Beans(6): 作用域

    Spring 支持五种作用域,分别是 singleton.prototype.request.session 和 global session. 作用域 说明  singleton (默认作用域)单例 ...

  2. 在c#中使用log4net

    1.从log4net官网下载最新的log4net.dll 2.设置配置文件在app.config <?xml version="1.0"?> <configura ...

  3. asp.net常见面试题(一)

    1.索引器 class Player { ]; public int this[int index] { get { || index >= ) { ; } else { return arr[ ...

  4. AngularJS Boostrap Pagination Sample

    首先,样式是这样的 首先,Service端是Webapi REST JSON格式 第二,我们建立一个Wrapper Class,这里你也可以定义一个Generic<T>,作为示例,我们这里 ...

  5. 晒下自己App广告平台积分墙收入,顺便点评几个广告平台

    这是我之前发在爱开发App源码论坛的文章.分享了我从2011年到现在移动广告方面的收入和一些心得. 产品类型:FC.街机模拟器类App游戏 广告平台:万普世纪 广告形式:积分墙,用户先试玩几次,再玩需 ...

  6. 安装和启动mongodb数据库

    参考链接:http://www.fkblog.org/blog569 参考链接:http://www.cnblogs.com/linjiqin/p/3192159.html

  7. selenium Grid(一)

    selenium grid Quick Start selenium-grid是用于设计帮助我们进行分布式测试的工具,其整个结构是由一个hub节点和若干个代理节点组成.hub用来管理各个代理节点的注册 ...

  8. JS 截取字符串函数

    一.函数:split() 功能:使用一个指定的分隔符把一个字符串分割存储到数组 例子: str=”jpg|bmp|gif|ico|png”; arr=theString.split(”|”); //a ...

  9. Z-Stack内部API 小结

    Z-Stack是TI推出的全功能ZigBee协议栈,通过了ZigBee联盟的兼容性平台测试,包含如下几个组件. 1. HAL,硬件抽象层 2. OSAL,操作系统抽象层 3. ZigBee Stack ...

  10. ASP.NET的错误处理机制

    对于一个Web应用程序来说,出错是在所难免的,因此我们应该未雨绸缪,为可能出现的错误提供恰当的处理.事实上,良好的错误处理机制正是衡量Web应用程序好坏的一个重要标准.试想一下,当用户不小心在浏览器输 ...