Dotnet跨应用程序域访问和需要特别注意的地方(WCF消息通道处于错误状态异常中的一种情况)
2012-02-06 19:34 java线程例子 阅读(323) 评论(0) 编辑 收藏 举报今天在做分布式计算原型的时候出现了WCF调用错误,错误大意是指消息通道处于错误状态,经过跟踪调试,发现是由于跨域调用的问题造成的.
问题场景是这样:客户端C通过WCF调用远程服务,该服务S启用一个新的应用程序域AD1,动态加载目标程序集,并执行该程序集中类CL的一个方法M,并将该方法的返回值(类型为T1)返回客户端C.这个应用程序域AD1虽然与主应用程序域都属于同一进程,但并不能直接相互访问,必须通过远程调用来实现,当然,由于都是同一个进程下,只要参与这个远程调用的对象都是继承自MarshalObjectByRef,这种远程调用并不需要象通常的那样通过remoting来实现,DotNet会自动处理。 Dotnet采用的是透明代理的方法(类似于我前面关于Spring技术博文中的AOP实现的方法),代理类是动态产生的。一般情况下这没有什么问题,但如果将调用目标方法返回的对象直接返回给WCF客户端,问题就出来了,就会报消息通道处于错误状态的异常。为什么会出现这种情况呢,原因如下:由于参与这个过程的类都会产生一个代理类,而且是动态产生的,因此调用方法M返回对象的实际类型并不是T1,而是T1 的一个代理子类TP1,注意,由于TP1是T1 的子类,基于面向对象继承的原则,父类可以用的地方,子类都可以用,因此编译时和运行时都不会出现类型错误。一般情况下这也没有问题,因为你使用T1类型变量是可以访问该代理对象的,但直接将该返回值对象作为WCF服务方法的返回值就有问题了,因为WCF对返回值进行序列化的时候是针对实际类型的(就是对象实例实际的类型,上面的调用中就是TP1),服务端用这个类型序列化没问题,因为类型TP1是存在的,但到客户端时,并不能反序列化回来,因为代理类TP1是动态产生的,只在服务端存在,客户端并不存在,所以就会报错。当然,这个错误报的有点笼统,害得我调试了半个小时才找到原因(分布式程序调式本身就是个细活)。
找到问题的原因,解决的办法很简单,就是重新创建一个返回类型的实例,并将跨域返回的类型的值克隆到这个实例,然后返回新建的这个实例对象即可。
这个错误比较隐蔽,主要原因是跨应用程序域访问的代理对象是DotNet动态产生的,而且是不需要程序员去干预的。
PS:这也是C#中协变和抗变时需要特别注意的地方.
补充:在自己增加WCF宿主服务时,一个端口只能被一个应用所有, 如果想在一台机器上启动多个宿主服务应用,则需进行端口级别的分离,路径和服务名只能在一个应用中做区分。否则会报该地址端口监听已被占用之类的的错误。而且由于一个服务需要建立一个host,因此建议一般情况下还是不要自己做宿主服务,用iis,WAS承载比较好。
补充(2012-02-08):跨域调用时参与的类需要从MarshalByRefObject继承,而这个类最好不要用于WCF通信参数或返回值,会发生莫名其妙的序列化错误(有时候可以,有时候又不可以,但错误率很高)。结论就是用于跨应用程序域访问的参与类不要参与WCF通信。如果实在要参与,就需要对这种类设置标准合同属性(DataContractattribute,DateMemberAttribute).就为了调试这个错误,浪费了我2个小时有多。感觉这是微软WCF的一个bug,要么嘛,你就可以,要么嘛你就不行,有时候序列化正常,有时候序列化又出错(出在客户端代理这边).