zoukankan      html  css  js  c++  java
  • 使用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; }
        }
    View Code

    此外还定义了servicebase基类。

        public class ServiceBase
        {
        }
        

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

        public class FakeService : ServiceBase
        {
            public virtual int GetInt()
            {
                throw new Exception("");
                return 100;
            }
        }
    View Code

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

    以上是使用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;
            }
    View Code

    对应的结果如下:

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

    困境

    对于单纯的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 == 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();
            }
    View Code

    然后生成参数,这里先构造一个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;
            }
    View Code

    参数中的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();
                });
            }
    View Code

    这个方法返回一个委托,该委托接受一个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 == 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();
            }
    
            /// <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()[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;
            }
    
            /// <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();
        }
    }
    View Code

    以及一个辅助的缓存类:

    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 == 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();
            }
    
            /// <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()[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;
            }
    
            /// <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();
        }
    }
    View Code

    此时,我们就可以继续改造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;
            }
    View Code

    对应的结果如下:

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

    public virtual async Task<int> GetIntAsync()
            {
                return await Task.FromResult(1000);
            }

    结果如下:

  • 相关阅读:
    1021 个位数统计
    1020 月饼
    1019 数字黑洞
    1018 锤子剪刀布
    1017 A除以B
    1016 部分A+B
    1015 德才论
    1014 福尔摩斯的约会
    cocos2d 间隔动作
    cocos2d 瞬时动作
  • 原文地址:https://www.cnblogs.com/lightluomeng/p/4945919.html
Copyright © 2011-2022 走看看