使用Unity拦截一个返回Task的方法
目标
主要是想为服务方法注入公用的异常处理代码,从而使得业务代码简洁。本人使用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 ;
}
}
使用单元测试来调用这个方法,得到的结果:

以上是使用Unity在方法调用前后注入的例子,对于同步方法而言并不存在问题。由于.NET 4.5引入了async和await,异步方法变得常见,使用传统的方式注入变得行不通。其实,不仅仅是async方法,所有awaitable的方法都存在这个问题。
原因很简单,对于同步方法(不可等待的方法)而言,调用前后就是内部执行调用的前后,而对于返回Task类型的方法而言,调用结束后,异步操作并未结束,所以即使异步操作发生了异常,也无法被捕捉。这里使用一个异步方法进行实验:

由结果可见,拦截前后并没有发生异常,异常时在对Task对象等待的时候发生的。
方案
既然知道了为何无法拦截,那么就很容易得出方案:将拦截的范围延伸到Task方法执行完毕之后的点。首先,拿不带返回值的Task实验。对于Task而言,我们只关心Task结束后的操作,而我们又不需要为其返回一个对象。所以,我们一定可以使用一个参数签名为Action<Task>的ContinueWith来达到目标,修改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 = 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<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<>:
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 == && pars.First().ParameterType.Name.StartsWith("F");
}).First().MakeGenericMethod(returnType);
}
return methods.Where(i =>
{
var pars = i.GetParameters().ToList();
return pars.Count ==
&& 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()[];
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<T>,无论你想额外做什么时候,只需要编译一次ExpressionTree,这有利于ExpressionTree缓存从而提高性能。
最后就是编译ExpressionTree生成一个委托:
private Func<Task, Action<Task>, object> MakeContinueTaskFactory(Type taskType, bool hasReturn)
{
var key = taskType.FullName;
return ConcurrentDic.GetOrAdd(key, k =>
{
var actionParam = Expression.Parameter(typeof (Action<Task>));
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<Func<Task, Action<Task>, 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<Task, Action<Task>, object>>
{
/// <summary>
/// 获取Task的ContinueWith方法
/// </summary>
/// <param name="taskType"></param>
/// <param name="hasReturn"></param>
/// <returns></returns>
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 == && pars.First().ParameterType.Name.StartsWith("F");
}).First().MakeGenericMethod(returnType);
}
return methods.Where(i =>
{
var pars = i.GetParameters().ToList();
return pars.Count ==
&& pars.First().ParameterType.Name.StartsWith("A")
&& pars.First().ParameterType.IsGenericType;
})
.First();
} /// <summary>
/// 针对不同Task生成不同的ContinueWith委托
/// </summary>
/// <param name="taskType"></param>
/// <param name="actionExp"></param>
/// <param name="hasReturn"></param>
/// <returns></returns>
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()[];
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;
} /// <summary>
/// 为指定的Task类型编译一个ContinueWith的生成器
/// </summary>
/// <param name="taskType"></param>
/// <param name="hasReturn"></param>
/// <returns></returns>
private Func<Task, Action<Task>, object> MakeContinueTaskFactory(Type taskType, bool hasReturn)
{
var key = taskType.FullName;
return ConcurrentDic.GetOrAdd(key, k =>
{
var actionParam = Expression.Parameter(typeof (Action<Task>));
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<Func<Task, Action<Task>, object>>(callExp, taskParam, actionParam);
return lambda.Compile();
});
} /// <summary>
/// 为Task类型的对象注入代码
/// </summary>
/// <param name="task"></param>
/// <param name="action"></param>
/// <returns></returns>
public object Inject(Task task, Action<Task> 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<Task, Action<Task>, object>>
{
/// <summary>
/// 获取Task的ContinueWith方法
/// </summary>
/// <param name="taskType"></param>
/// <param name="hasReturn"></param>
/// <returns></returns>
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 == && pars.First().ParameterType.Name.StartsWith("F");
}).First().MakeGenericMethod(returnType);
}
return methods.Where(i =>
{
var pars = i.GetParameters().ToList();
return pars.Count ==
&& pars.First().ParameterType.Name.StartsWith("A")
&& pars.First().ParameterType.IsGenericType;
})
.First();
} /// <summary>
/// 针对不同Task生成不同的ContinueWith委托
/// </summary>
/// <param name="taskType"></param>
/// <param name="actionExp"></param>
/// <param name="hasReturn"></param>
/// <returns></returns>
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()[];
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;
} /// <summary>
/// 为指定的Task类型编译一个ContinueWith的生成器
/// </summary>
/// <param name="taskType"></param>
/// <param name="hasReturn"></param>
/// <returns></returns>
private Func<Task, Action<Task>, object> MakeContinueTaskFactory(Type taskType, bool hasReturn)
{
var key = taskType.FullName;
return ConcurrentDic.GetOrAdd(key, k =>
{
var actionParam = Expression.Parameter(typeof (Action<Task>));
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<Func<Task, Action<Task>, object>>(callExp, taskParam, actionParam);
return lambda.Compile();
});
} /// <summary>
/// 为Task类型的对象注入代码
/// </summary>
/// <param name="task"></param>
/// <param name="action"></param>
/// <returns></returns>
public object Inject(Task task, Action<Task> 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<T>的方法:
public virtual async Task<int> GetIntAsync()
{
return await Task.FromResult();
}
结果如下:

使用Unity拦截一个返回Task的方法的更多相关文章
- .NET(C#):await返回Task的async方法
众所周知,async方法只可以返回void,Task和Task<T>. 对于返回void的async方法,它并不是awaitable,所以其他方法不能用await方法来调用它,而返回Tas ...
- Html5 监听拦截Android返回键方法详解
浏览器窗口有一个history对象,用来保存浏览历史. 如果当前窗口先后访问了三个网址,那么history对象就包括三项,history.length属性等于3. history对象提供了一系列方法, ...
- jquery ajax中支持哪些返回类型以及js中判断一个类型常用的方法?
1 jquery ajax中支持哪些返回类型在JQuery中,AJAX有三种实现方式:$.ajax() , $.post , $.get(). 预期服务器返回的数据类型.如果不指定,jQuery 将自 ...
- 求一个整型数字中有没有相同的部分,例如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 ...
- 058、Java中定义一个没有参数没有返回值的方法
01.代码如下: package TIANPAN; /** * 此处为文档注释 * * @author 田攀 微信382477247 */ public class TestDemo { public ...
- 在finally中调用一个需要await的方法
最近在把code改写成async+await的形式,发现有些情况下需要在finally中需要调用异步方法,但是编译器不允许在cache和finally中出现await关键字...但是用Wait()或者 ...
- .Net4.0如何实现.NET4.5中的Task.Run及Task.Delay方法
前言 .NET4.0下是没有Task.Run及Task.Delay方法的,而.NET4.5已经实现,对于还在使用.NET4.0的同学来说,如何在.NET4.0下实现这两个方法呢? 在.NET4.0下, ...
- struts2拦截器interceptor的配置方法及使用
转: struts2拦截器interceptor的配置方法及使用 (2015-11-09 10:22:28) 转载▼ 标签: it 365 分类: Struts2 NormalText Code ...
- C#处理MySql多个返回集的方法
本文实例讲述了C#处理MySql多个返回集的方法.分享给大家供大家参考.具体方法如下: 关于Mysql返回多个集java和Php的较多,但是C#的完整代码好像没见过,研究了一下做个封装以后用 做一个M ...
随机推荐
- Mac环境下svn的使用(转载)
在Windows环境中,我们一般使用TortoiseSVN来搭建svn环境.在Mac环境下,由于Mac自带了svn的服务器端和客户端功能,所以我们可以在不装任何第三方软件的前提下使用svn功能,不过还 ...
- NVelocity 实现简单的 CIUD
1, NVelocity 是 一般处理程序.ashx 和 前台页面模板的桥梁. 2,我们现在建立一个简单的查询: A,新建项目,把NVelocity.dll拉入项目中,并添加对其引用 B,新建C ...
- nginx限制ip请求次数 以及并发次数
如何设置能限制某个IP某一时间段的访问次数是一个让人头疼的问题,特别面对恶意的ddos攻击的时候.其中CC攻击(Challenge Collapsar)是DDOS(分布式拒绝服务)的一种,也是一种常见 ...
- 【Cocos2d入门教程八】浅析Cocoss2d下的音频引擎及封装音频类
Cocos2d-x提供了一个音频CocosDenshion引擎,CocosDenshion引擎可以独立于Cocos2d-x单独使用,CocosDenshion引擎本质上封装了OpenAL音频处理库.具 ...
- 会话—cookie
什么是会话? 会话可简单理解为:用户开一个浏览器,点击多个超链接,访问服务器多个web资源,然后关闭浏览器,整个过程称之为一个会话. 会话过程中要解决的一些问题? 每个用户在使 ...
- win7下将主分区转换成逻辑分区
在了解怎么转换之前,先搞清楚主分区,扩展分区,逻辑分区的基本概念. 主分区,也称为主磁盘分区,和扩展分区.逻辑分区一样,是一种分区类型.主分区中不能再划分其他类型的分区,因此每个主分区都相当于一个逻辑 ...
- ViewPager的简单例子
这个例子是按照官网上的例子写的,有点抄袭的嫌疑,但是自己单独写一下会加深自己的印象. 首先是MainAcitivity.xml: <LinearLayout xmlns:android=&quo ...
- ubuntu windows 双系统 磁盘乱搞 grub 导致 error:no such partition grub rescue>
乱搞分区磁盘什么的导致开机和这位仁兄一样:http://zhidao.baidu.com/question/495602654256708364.html?ssid=0&from=100187 ...
- vim ctags 的使用
ubantu 先安装 sudo apt-get install ctags 『基本功能使用方法』常用命令列表: 1. $ ctags –R * ($ 为Linux系统Shel ...
- C#实现打印与打印预览功能
C#实现打印与打印预览功能的思路及代码. 在windows应用程序中文档的打印是一项非常重要的功能,在以前一直是一个非常复杂的工作,Microsoft .Net Framework的打印功能都以组件的 ...