none
WCF从理论到实践(8):事件广播 RRS feed

  • 常规讨论

  • 上文讨论了WCF中三种消息交换模式,one-way,request/reply,duplex。前两项比较简单,无需多言,duplex相对比较复杂,上文只是实现了简单的回调,在真正应用的时候,还有许多值得注意之处,本文就结合一个实际的应用例子来谈论下duplex的具体应用和非常值得我们注意的地方。

    本文的出发点

    通过阅读本文,您能理解以下知识:

    1. 如何实现一个基于duplex的事件广播
    2. 解析在实现duplex事件广播中的几个问题
    3. 初步探讨一下异步

    本文适合的读者

    本文属于中等难度的文章,需要有WCF消息交换和windows应用程序开发相关的基础知识,有关WCF消息交换,请阅读http://www.cnblogs.com/jillzhang/archive/2008/02/17/1071521.html

    如何实现一个基于duplex的事件广播

    在讨论如何实现之前,先看一下本文的范例所要实现的功能是什么?本文的范例实现了一个简单的分布式任务管理系统,简单的说,它是在服务端(Server Point)执行任务(Job),并且将任务的信息呈现给客户端。它有如下特征:

    1. 通过调用服务端的Accept(),客户端能连接上服务端,并保持会话。
    2. 客户端在启动的时候,可以通过远程调用GetJobs()来获取当前服务端中全部的任务,并将这些任务在客户端窗体中用列表控件呈现出来
    3. 客户端能通过调用AddJob()向服务端添加任务,当服务端完成添加操作之后,引发添加完成的事件,并向全部的客户端广播该事件
    4. 当客户端服务端发来的添加新任务事件广播的时候,客户端将新增任务添加到列表控件加以呈现
    5. 客户端可以命令服务端执行具体某个任务,当任务在开始执行和执行结束后,服务端都会像全部客户端广播任务的执行情况,并且任务的执行和事件的广播异步执行
    6. 客户端收到广播后,便可以更新任务信息。

    和以前文章不同,本文先给出最后实现的效果

    如何您要了解该范例得具体设计和实现,可以下载下面的文件进行分析:
    范例最终实现:/Files/jillzhang/Jillzhang.Event.rar
    我这里只列出范例中项目列表

    项目名称

    项目描述

    Jillzhang.Event.Core

    该项目用于定义WCF的契约,主要包括IServer服务契约,ICallback用于回调的服务契约,Job数据契约

    Jillzhang.Event.Service

    服务端的具体实现,其中Server实现了一个有广播事件能力的服务契约

    Jillzhang.Event.Host

    服务的宿主程序,一个ConsoleApplication

    Jillzhang.Event.Client

    客户端实现,用于消费服务端。

    Jillzhang.Event.Client2

    和Jillzhang.Event.Client是一个实现,但为了验证广播,可与Jillzhang.Event.Client同时消费服务端

     

    解析在实现duplex事件广播中的几个问题

    1) Duplex模式对服务行为ConcurrencyMode的要求

    我们知道ConcurrencyMode是控制服务并发的,默认情况下ConCurrendMode的值为Single,它设置服务运行在单线程下,当上一个请求未完成之前,服务是不接受下一个请求的。而duplex在进行回调的时候,如果回调方法没有被设置为 One-Way的交换模式,服务端是会等待客户端对回调的响应的,这可不是一件好事情,因为服务端并不能保证客户端能正常地执行回调并返回数据。更多的情况下,我们期望回调在发出后能立即返回,方法有两个:a)将回调方法设置为One-Way交换模式 b)采用多线程。经过我的测试,当回调方法被设置了one-way模式后,将ConcurrencyMode设置为Single是可以实现duplex双向通讯的。 要第二种方法也非常简单,只需要将ConcurrencyMode设置为Mutiple.此时即使回调方法不是one-way模式,也是可以完成duplex的。值得说明一下的是ConcurrencyMode还有中性的属性:ConcurrencyMode.Reentrant,说句心里话,我不喜欢这个不伦不类的家伙,他能实现在单线程下同时接受多个请求,但有利必有弊,这个家伙不能保证请求事务的完整性,使用的时候应该谨慎。

    2)InstanceContextMode = InstanceContextMode.PerSession却为何能实现广播?

    如果将InstanceContextMode设置为PerSession,我们知道服务端对象是针对每一个会话的,也就是说每个会话会产生一个对象实例,这样如果要实现广播,我们必须将当前服务包含的会话信息用一个列表对象记录下来,广播的时候,我们遍历会话列表,进行逐个回调。本示例中巧妙的利用了Event可包括多个委托实例的特征,一个静态的Event对象针对每个会话创建一个委托实例便可以完成上述的要求。遍历回调的方法便可以编写如下:  

            private void BroadcastEvent(CallbackEventArg e, ServerEventHanlder temp)
            
    {         
                
    if (OnStatusChanged != null
    )
                
    {
                    
    foreach (ServerEventHanlder handler in
     temp.GetInvocationList())
                    
    {
                        handler.BeginInvoke(
    this, e, new AsyncCallback(EndAsync), null
    );
                    }

                }

            }

     

    3)困扰了我半天的问题

    上篇文章中,已经对duplex有了初步的认识,本以为本文的范例实现会很顺利呢,可有一个问题却困扰了我半天,在回调的时候非常不稳定,有时能回调4.5次,有时1,2次之后,再回调却没了响应,开始百思不得其解,因为开始几次可成功回调,为何会不稳定呢?经过好一番尝试,也没能解决,回调没有响应,肯定是客户端与服务端失去了连接,会话过期就会造成双方通讯连接的中断,经过分析,我的系统是这样的

    会不会在Accept后Do()方法前的过程中会话过期了呢?后来经过验证,的确是此处的问题,解决方法是通过设置操作契约的IsTerminating来实现会话的维护,当一个操作契约的IsTerminating被设置为false的时候,该操作不会导致会话的中断,将IServer设计如下便解决了我的问题 :

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.ServiceModel;

    namespace Jillzhang.Event.Core
    {
        [ServiceContract(SessionMode 
    = SessionMode.Required, CallbackContract = typeof(ICallback))]
        
    public interface IServer
        
    {
            [OperationContract(IsOneWay 
    = true, IsInitiating = true, IsTerminating = false)]
            
    void Accept();        

            [OperationContract(IsOneWay
    =true,IsInitiating=false,IsTerminating=false)]
            
    void Do(string jobName);

            [OperationContract(IsOneWay 
    = true, IsInitiating = false, IsTerminating = false)]
            
    void AddJob(Job job);

            [OperationContract(IsOneWay
    =false,IsInitiating=false,IsTerminating=false)]
            List
    <Job> GetJobs();
        }

    }


    初步探讨一下异步

    进程间通讯是一件很耗时的事情,如果同步执行会造成线程的阻塞,如果是在服务端,会降低服务的处理能力(这种说法可能有些问题,我会进一步求证,估计保留,经过查证多线程在服务端的好处在于提供对单个请求用多个线程处理的能力,从而防止完成一个请求之前,无法接受新的请求),如果是在客户端,会给用户带来不好的体验。下面就分别探讨一下如何实现服务端和客户端的异步。

    在服务端,一个事件的异步可以通过delegate的BeginInvoke和EndInvoke来实现,具体方法可以参见示例项目Jillzhang.Event.Service中的Server对象的方法BroadcastEvent方法的实现

    而在客户端,我们可以起多个线程,当然最方便快捷的办法就是使用BackGroundWorker后台线程来处理耗时比较长的操作了,具体实现也可以参考Jillzhang.Event.Client项目中的Form1.cs实现。

    本文的参考资料

    1. http://www.cnblogs.com/wayfarer/archive/2007/03/08/667865.html
    2. http://www.cnblogs.com/caishiqi/archive/2007/10/05/914671.html
    3. http://msdn.microsoft.com/msdnmag/issues/06/10/wcfessentials/default.aspx
    2009年6月30日 8:53