漫谈Silverlight(1)封装异步操作

    承接上篇,继续来讨论异步操作的话题。

    在上一篇的讨论中,有园友提出了Service的粒度问题,我觉得说的很有道理,对于Silverlight的RIA应用,合理的设计Service的接口,控制粒度也是与传统的编程思路有差异的地方,对原子操作的合并再发布对于RIA的Service设计是很必要的,我把它称作合理的重用其它Service,关于这方面的经验,还请大家积极讨论分享设计经验,我这全当是抛砖引玉了。

    言归正传,既然我们无可避免要适应异步的编程模型,那么让我们来尝试将异步操作封装的更加易于操作吧。我认为异步操作最麻烦的就是异步上下文的传递,在一段逻辑上连续的操作中,通常伴随着“全局”变量,这个“全局”变量可能在某个Page上或者某个UserControl上,亦或就是在某个静态变量上,这种类似的编程经验让我们闻到了一种危险的味道,大量“全局”的变量会使得我们的程序越来越难理解和维护,这也促使我决定改造ServiceClient的使用,下面分享我的一些拙见。

    微软为我们提供的ServiceClient是基于事件的异步模型,这点让我感觉使用起来很不爽,我比较习惯于callback的编程模式,事件是多播的,使用不当很容易造成多次订阅,我的一个同事就犯过类似的错误,而且对于返回值的使用也让我很不爽,我实在搞不懂微软为什么要这样暴露ServiceClient,我希望的使用模式类似于这样:(以上篇的GetTaskAsync为例)

 
 
  1:         //封装方式
  2:         private void GetTaskAsync(string taskId, Action<Task> callback);
  3:         //调用方式
  4:         private void Test4()
  5:         {
  6:             TestServiceClient client = new TestServiceClient();
  7:             client.GetTaskAsync("task id", (task) =>
  8:             {
  9:                 //work with task
 10:             });
 11:         }

    service中的N个参数作为客户端相应Async方法的前N的参数保持一致(N可以为0),另外再加一个Action<返回值>作为最后一个参数(如果service方法中无返回值则为Action),而Async方法的返回值始终为void,我这样封装的一个理由是在Action的回调中我可以继续用类似的方式调用Service,而整个上下文在各个操作中都是共享的:

 
 
  1:         private void Test4()
  2:         {
  3:             TestServiceClient client = new TestServiceClient();
  4:             object bizObject = new object();
  5:             client.GetTaskAsync("task id", (task) =>
  6:             {
  7:                 //bizObject.xxx()
  8:                 client.DoWorkAsync(() =>
  9:                 {
 10:                     //bizObject.yyy()
 11:                     client.DoOtherWorkAsync(() =>
 12:                     {
 13:                         //bizObject.zzz()
 14:                     });
 15:                 });
 16:             });
 17:         }

    我个人比较喜欢这样的代码,虽然要注意.Net环境下的“假上下文”所产生的副作用问题(参见老赵的这篇文章“警惕匿名方法造成的变量共享”),但这段代码看起来更像是“同步”操作,代码似乎在连续的执行,你可以在每个Action的开始打上断点连续的调试,给你的感觉代码就像是在顺序执行一样,注意只是感觉上是,那我们应该怎么样实现呢?

 
 
  1:         private void GetTaskAsync(string taskId, Action<Task> callback)
  2:         {
  3:             TestServiceClient client = new TestServiceClient();
  4:             client.GetTaskCompleted += (s, args) =>
  5:             {
  6:                 callback(args.Result);
  7:             };
  8:             client.GetTaskAsync(taskId);
  9:         }

    这样看起来似乎是那么回事了,跟我们最终想要的形式很接近了,但我们总不能为每一个Service操作写上这样一段代码吧,你会说可以把某一个Service的操作封装到一个单独的类中,然后类中都是这样的代码,暴露这样的接口,像这样:

 
 
  1:     public class MyTestServiceClient
  2:     {
  3:         private TestServiceClient client;
  4:         public MyTestServiceClient()
  5:         {
  6:             TestServiceClient client = new TestServiceClient();
  7:             client.GetTaskCompleted += new EventHandler<GetTaskCompletedEventArgs>(client_GetTaskCompleted);
  8:         }
  9:         private void client_GetTaskCompleted(object sender, GetTaskCompletedEventArgs e)
 10:         {
 11:             var action = e.UserState as Action<Task>;
 12:             if (action != null) action(e.Result);
 13:         }
 14:         public void GetTaskAsync(string taskId, Action<Task> callback)
 15:         {
 16:             client.GetTaskAsync(taskId, callback);
 17:         }
 18:         //...
 19:     }

    这样看起来比较舒服了,我们可以用MyTestServiceClient替代TestServiceClient来实现上面的代码,可同时我发现这要做大量重复的Coding,我为每个Service接口订阅事件处理函数,事件处理函数里面处理callback,再暴露每个Async调用的接口方法,我们似乎在做svcutil.exe该做的事情,有人说可以啊!就做代码生成,有兴趣的童鞋可以试试这个:)

    既然是重复的事情就可以找到规律,就可以让我们找到方法再次封装,受到上篇文章中提到的封装同步的代码的启发,我想到了泛型,还想到了反射,先看看这个牛人是如何封装他所谓的在异步中同步的代码的:

 
 
  1:     void CallWcfSynchronouslyUsingChannelManager()
  2:     {
  3:       var simpleService = ChannelManager.Instance.GetChannel<ISimpleService>();
  4:       string result = string.Empty;
  5:       try
  6:       {
  7:         /* Perform synchronous WCF call. */
  8:         result = SynchronousChannelBroker.PerformAction<string, string>(
  9:           simpleService.BeginGetGreeting, simpleService.EndGetGreeting, "there");
 10:       }
 11:       catch (Exception ex)
 12:       {
 13:         DisplayMessage(string.Format("Unable to communicate with server. {0} {1}",
 14:           ex.Message, ex.StackTrace));
 15:       }
 16:       DisplayGreeting(result);
 17:     }

    可以看到,他的封装方式是:TReturn PerformFunction<TArg1,TArg2,…>(Service.Beging…,Service.End…,TArg1 arg1,TArg2 arg2,…);

    而我的封装方式也是类似的:void PerformFunction<TArg1,TArg2,…>(Service.…Async,TArg1 arg1,TArg2 arg2,…,Action<TReturn> callback);

    并且为了适应任意的Service我将基类做成了泛型类并命名为ServiceProxy:

 
 
  1:     public class ServiceProxy<T> where T : ICommunicationObject
  2:     {
  3:         private T serviceClient;
  4:         public ServiceProxy(T serviceClient)
  5:         {
  6:             this.serviceClient = serviceClient;
  7:         }
  8:         public T ServiceClient
  9:         {
 10:             get
 11:             {
 12:                 return this.serviceClient;
 13:             }
 14:         }
 15:     }

    最简单的情况,如果被封装的ServiceClient包含一个无参数无返回值的方法,则在ServiceProxy中的对外接口是这样的:

 
 
  1:         public void PerformFunction(Action function, Action<Exception> callback)
  2:         {
  3:             var func = GetFunc(function);
  4:             var userState = callback;
  5:             func.Invoke(function.Target, new object[] { userState });
  6:         }

    代码很少,关键的代码在于GetFunc方法中,要从传入的function中提取所有的信息,首先要找到最后一个参数为object UserState的同名方法:

 
 
  1:         private static MethodInfo GetMethodWithUserState(Delegate function)
  2:         {
  3:             var paramTypes = function.Method.GetParameters()
  4:                 .Select(info => info.ParameterType)
  5:                 .Concat(new Type[] { typeof(object) })
  6:                 .ToArray();
  7:             return function.Target.GetType()
  8:                 .GetMethod(function.Method.Name, paramTypes);
  9:         }

    然后是获得该方法的Completed事件以及事件相应的委托:

 
 
  1:         private static Tuple<EventInfo, Delegate> GetEventAndHandler(Delegate function)
  2:         {
  3:             var srcMethod = function.Method;
  4:             var methodName = srcMethod.Name;
  5:             if (!methodName.EndsWith("Async"))
  6:                 throw new ArgumentException(methodName + " must be ends with Async");
  7:             var target = function.Target;
  8:             var targetType = target.GetType();
  9:             var completeEventName = methodName.Substring(0, methodName.Length - 5) + "Completed";
 10:             EventInfo eventInfo = targetType.GetEvent(completeEventName);
 11:             var eventArgsName = completeEventName + "EventArgs";
 12:             Type eventArgsType = targetType.Assembly.GetType(targetType.Namespace + "." + eventArgsName);
 13:             var handlerMethod = CompleteEventHandlerMethodInfo.MakeGenericMethod(eventArgsType);
 14:             Type handlerType = Type.GetType(String.Format("System.EventHandler`1[[{0},{1}]]", eventArgsType.FullName, AssemblyName));
 15:             Delegate handler = Delegate.CreateDelegate(handlerType, handlerMethod);
 16:             return new Tuple<EventInfo, Delegate>(eventInfo, handler);
 17:         }

    看完这段代码,先说个题外话,这里我用了Tuple作为返回值,因为要返回两个值,您可能会觉得不专业,我倒是觉得在返回多个值的场景下很实用(至少比用out看起来更舒服),虽然这可能违背了Tuple的设计初衷,问题是我也不知道Tuple是为了什么场景而设计的,希望高手指点:)

    至于获得事件和委托的实现,如果您了解并熟悉反射的相关知识,代码并不难懂,只需要注意如何生成泛型的方法的写法System.EventHandler`1[[TypeFullName,AssemblyName]],获得了所有我们需要的参数之后,我们就可以动态构造我们想要的一切了,这里我们是从CompleteEventHandlerMethodInfo中为泛型版本生成具体类型的方法的,那CompleteEventHandlerMethodInfo是什么呢:

 
 
  1:         private static MethodInfo completeEventHandlerMethodInfo;
  2:         private static MethodInfo CompleteEventHandlerMethodInfo
  3:         {
  4:             get
  5:             {
  6:                 if (completeEventHandlerMethodInfo == null)
  7:                 {
  8:                     var currentType = typeof(ServiceProxy<T>);
  9:                     completeEventHandlerMethodInfo = currentType.GetMethod("CompleteEventHandler");
 10:                 }
 11:                 return completeEventHandlerMethodInfo;
 12:             }
 13:         }
 14:         public static void CompleteEventHandler<TArgs>(object sender, TArgs eventArgs)
 15:             where TArgs : AsyncCompletedEventArgs
 16:         {
 17:             if (eventArgs.Cancelled) return;
 18:             if (eventArgs.Error != null)
 19:             {
 20:                 if (eventArgs.UserState is Action<Exception>)
 21:                     (eventArgs.UserState as Action<Exception>)(eventArgs.Error);
 22:                 else if (eventArgs.UserState is Action<object, Exception>)
 23:                     (eventArgs.UserState as Action<object, Exception>)(null, eventArgs.Error);
 24:                 else
 25:                     throw eventArgs.Error;
 26:             }
 27:             else
 28:             {
 29:                 if (eventArgs.UserState is Action<Exception>)
 30:                     (eventArgs.UserState as Action<Exception>)(null);
 31:                 else if (eventArgs.UserState is Action<object, Exception>)
 32:                 {
 33:                     object result = eventArgs.GetType().GetProperty("Result").GetValue(eventArgs, null);
 34:                     (eventArgs.UserState as Action<object, Exception>)(result, null);
 35:                 }
 36:             }
 37:         }
 38: 

    CompleteEventHandlerMethodInfo所指向的是static void CompleteEventHandler<TArgs>(object sender, TArgs eventArgs) where TArgs : AsyncCompletedEventArgs

    这里处理的两种回调,将异常一起考虑在内,在异常发生时,异常将作为回调函数的第二个参数被传入,结果则被传入null,这样在回调中我们可以对Service访问的异常也进行处理,另外此处我还有一个疑问,虽然在AsyncCompletedEventArgs上提供了Cancelled属性,但是我不知道如何cancel一个已经调用了的Service操作,我并没有找到类似的Cancel方法,所以还是要请教高人,我如何Cancel一个Service操作呢?

    在获得调用方法的同时,我们同时获得了该方法相应的Completed事件和事件处理函数,剩下的工作就是组合这些操作了:

 
 
  1:         protected MethodInfo GetFunc(Delegate function)
  2:         {
  3:             if (!function.Target.Equals(this.ServiceClient))
  4:                 throw new Exception("must use ServiceClient object");
  5:             if (!this.FunctionCache.ContainsKey(function))
  6:             {
  7:                 var func = GetMethodWithUserState(function);
  8:                 var args = GetEventAndHandler(function);
  9:                 args.Item1.AddEventHandler(function.Target, args.Item2);
 10:                 FunctionCache.Add(function, func);
 11:             }
 12:             return FunctionCache[function];
 13:         }

    将事件处理函数订阅到事件,然后缓存在FunctionCache中,这样再次调用同一方法时就不用再次反射了,至此,我们再回头看看本文开始的那段代码使用ServiceProxy后是什么模样吧:

 
 
  1:     var client = new TestServiceClient();
  2:     var proxy = new ServiceProxy<TestServiceClient>(client);
  3:     proxy.PerformFunction(client.GetTaskAsync,"task id",(Exception error,Task task)=>
  4:     {
  5:         if(error == null)
  6:         {
  7:             //do work with task
  8:             task.xxx();
  9:         }
 10:         else
 11:         {
 12:             //error handle
 13:         }
 14:     });

    在使用时要注意的一个限制就是PerformFunction的第一个参数必须是ServiceProxy代理的对象发出的,并且有了类型推断使得我们不必在PerformFunction后写上<string>这样的代码,看起来还是很舒服的,完整代码可以在文章最后找到。

    总结一下,也许你会觉得这样的封装怪模怪样,但我的目的仅在于让我们的代码更优雅,更易于维护,通俗点说就是看起来更舒服,您如果有不同看法欢迎一起讨论,并且本文中留下了几个疑问,希望大家赐教:

    (1)Tuple是为了什么需求而设计的?

    (2)如何Cancel一个Service操作?

    另外,我在具体使用上述代码的过程中碰到一个问题,对于Service中定义的方法如果含有out object arg的时候,在Silverlight中是将out object作为AsyncCompletedEventArgs的一个属性返回的,而参数列表中并没有,所以对于包含out修饰的Service接口只能在CompleteEventHandler中再次进行反射得到,这可能是个小小的遗憾,但是无论怎样,我关于异步操作的讨论暂且到此,剩下的留给大家评说,希望本文对您有帮助。

    完整代码下载

转载于:https://www.cnblogs.com/devil0153/archive/2010/06/09/Async-ServiceProxy.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在现有省、市港口信息化系统进行有效整合基础上,借鉴新 一代的感知-传输-应用技术体系,实现对码头、船舶、货物、重 大危险源、危险货物装卸过程、航管航运等管理要素的全面感知、 有效传输和按需定制服务,为行政管理人员和相关单位及人员提 供高效的管理辅助,并为公众提供便捷、实时的水运信息服务。 建立信息整合、交换和共享机制,建立健全信息化管理支撑 体系,以及相关标准规范和安全保障体系;按照“绿色循环低碳” 交通的要求,搭建高效、弹性、高可扩展性的基于虚拟技术的信 息基础设施,支撑信息平台低成本运行,实现电子政务建设和服务模式的转变。 实现以感知港口、感知船舶、感知货物为手段,以港航智能 分析、科学决策、高效服务为目的和核心理念,构建“智慧港口”的发展体系。 结合“智慧港口”相关业务工作特点及信息化现状的实际情况,本项目具体建设目标为: 一张图(即GIS 地理信息服务平台) 在建设岸线、港口、港区、码头、泊位等港口主要基础资源图层上,建设GIS 地理信息服务平台,在此基础上依次接入和叠加规划建设、经营、安全、航管等相关业务应用专题数据,并叠 加动态数据,如 AIS/GPS/移动平台数据,逐步建成航运管理处 "一张图"。系统支持扩展框架,方便未来更多应用资源的逐步整合。 现场执法监管系统 基于港口(航管)执法基地建设规划,依托统一的执法区域 管理和数字化监控平台,通过加强对辖区内的监控,结合移动平 台,形成完整的多维路径和信息追踪,真正做到问题能发现、事态能控制、突发问题能解决。 运行监测和辅助决策系统 对区域港口与航运业务日常所需填报及监测的数据经过科 学归纳及分析,采用统一平台,消除重复的填报数据,进行企业 输入和自动录入,并进行系统智能判断,避免填入错误的数据, 输入的数据经过智能组合,自动生成各业务部门所需的数据报 表,包括字段、格式,都可以根据需要进行定制,同时满足扩展 性需要,当有新的业务监测数据表需要产生时,系统将分析新的 需求,将所需字段融合进入日常监测和决策辅助平台的统一平台中,并生成新的所需业务数据监测及决策表。 综合指挥调度系统 建设以港航应急指挥中心为枢纽,以各级管理部门和经营港 口企业为节点,快速调度、信息共享的通信网络,满足应急处置中所需要的信息采集、指挥调度和过程监控等通信保障任务。 设计思路 根据项目的建设目标和“智慧港口”信息化平台的总体框架、 设计思路、建设内容及保障措施,围绕业务协同、信息共享,充 分考虑各航运(港政)管理处内部管理的需求,平台采用“全面 整合、重点补充、突出共享、逐步完善”策略,加强重点区域或 运输通道交通基础设施、运载装备、运行环境的监测监控,完善 运行协调、应急处置通信手段,促进跨区域、跨部门信息共享和业务协同。 以“统筹协调、综合监管”为目标,以提供综合、动态、实 时、准确、实用的安全畅通和应急数据共享为核心,围绕“保畅通、抓安全、促应急"等实际需求来建设智慧港口信息化平台。 系统充分整合和利用航运管理处现有相关信息资源,以地理 信息技术、网络视频技术、互联网技术、移动通信技术、云计算 技术为支撑,结合航运管理处专网与行业数据交换平台,构建航 运管理处与各部门之间智慧、畅通、安全、高效、绿色低碳的智 慧港口信息化平台。 系统充分考虑航运管理处安全法规及安全职责今后的变化 与发展趋势,应用目前主流的、成熟的应用技术,内联外引,优势互补,使系统建设具备良好的开放性、扩展性、可维护性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值