在瑟维斯的点子中

《Windows Communication Foundation之旅》种类之3

聊天室实例:点此下载 

演示代码下载:DuplexSample.rar

    我在《Windows Communication
Foundation之旅•三
》中详尽介绍了WCF中的Duplex新闻交流形式。因为Duplex达成了客户端与服务端双向通讯的功效,故而自笔者实现了1个简便的聊天室程序,彰显Duplex的特点。有情侣在阅读了那些事例之后,提出3个标题,即“如何让服务端向钦点的客户端发送音讯?”很欢快的是,这位情人在新生的邮件中提及难题早已缓解了,思路是采用Singleton对象保存客户端的Session。就算存在部分相比奇怪的题材,不过终归是一种思路。

四、Service Contract编制程序模型
在Part Two中,笔者以“Hello
World”为例讲解了哪些定义一个Service。其宗旨正是为接口或类施加ServiceContractAttribute,为艺术施加OperationContractAttribute。在Service的艺术中,能够承受五个参数,也足以有重返类型,只要那个数据类型能够被系列化。那样一种形式壹般被叫作本地对象,远程进程调用(local-object,
Remoting-Procedure-Call)情势,它那几个便宜开发人士火速地开始展览Service的花费。

   
作者的笔触与之相似,要求服务端维护2个Dictionary的集纳,用以保存客户端的音讯。服务端在发送新闻时,能够由此寻找Dictionary对象,识别符合条件的客户端。当自个儿还在思考那样的办法是还是不是缓解难题时,作者在WCF官方网址上间或发现了三个平等采用Duplex达成聊天室的Sample。

在Service Contract编制程序模型中,还有一种艺术是根据Message
Contract的。服务的法子最多只可以有3个参数,以及2个重回值,且它们的数据类型是经过Message
Contract自定义的消息类型。在自定义新闻中,能够为音讯定义详细的Header和Body,使得对音讯的置换更灵活,也更方便人民群众对信息的决定。

   
仔细翻阅了实例代码,作者忽然发现本人在揣摩程序设计时,并从未精晓WCF最基本的价值,这正是“服务”。作为贯彻SOA连串架构的技能框架,WCF最关键的特色就在于能够定义和提供劳动。以聊天室程序为例,尽管服务端会参与音讯的竞相,但却不应该参预到聊仲夏。也正是说,客户端与服务端的剧中人物职分是不1样的。通过用例图可以旁观两者之间的界别: 

一个幽默的话题是当我们定义1个Service时,假若三个private方法被施加了OperationContractAttribute,那么对于客户端而言,那么些措施是能够被调用的。这不啻与private对于指标封装的意思有抵触。不过如此的规定是有其现实意义的,因为对此3个劳动而言,服务端和客户端的必要往往会不1样。在服务端,该服务对象正是允许被远程调用,但地面调用却或者会因景况而异。如上面包车型客车服务概念:
[ServiceContract]
public class BookTicket
{
 [OperationContract]
 public bool Check(Ticket ticket)
 {
  bool flag;
  //logic to check whether the ticket is none;
  return flag;
 }
 [OperationContract]
 private bool Book(Ticket ticket)
 {
  //logic to book the ticket
 }
}
在服务类BookTicket中,方法Check和Book都以劳务格局,但后者被定义成为private方法。为啥吗?因为对于客户而言,首先会检查是不是还有电影票,不过再预约该电影票。也正是说那两项成效都是面向客户的劳动,会被远程调用。对于Check方法,除了远程客户会调用该方法之外,还有不小大概被询问电影票、预订电影票、出售电影票等工作逻辑所调用。而Book方法,则只针对中长途客户,只恐怕被远程调用。为了确认保障该办法的安全,将其设置为private,使得地点对象不至于调用它。

图片 1
图一  正确的用例图            

为此在WCF中,二个措施是不是合宜棉被服装置为劳动措施,以及相应设置为public依然private,都亟待基于实际的事体逻辑来判断。要是波及到村办的服务章程较多,一种好的点子是使用设计情势的Façade形式,将这个办法结合起来。而这么些点子的真人真事逻辑,或许会分散到各自的地头对象中,对于这一个本地对象,也能够给予一定的访问限制,如下边包车型客车代码所示:
internal class BusinessObjA
{
 internal void FooA(){}
}
internal class BusinessObjB
{
 internal void FooB(){}
}
internal class BusinessObjC
{
 internal void FooC(){}
}
[ServiceContract]
internal class Façade
{
 private BusinessObjA objA = new BusinessObjA();
 private BusinessObjB objB = new BusinessObjB();
 private BusinessObjC objC = new BusinessObjC();
 [OperationContract]
 private void SvcA()
 {
  objA.FooA();
 }
 [OperationContract]
 private void SvcB()
 {
  objB.FooB();
 }
 [OperationContract]
 private void SvcC()
 {
  objC.FooC();
 }
}
主意FooA,FooB,FooC作为internal方法,拒绝被先后集外的地点对象调用,但SvcA,SvcB和SvcC方法,却得以被远程对象所调用。大家居然足以将BusinessObjA,BusinessObjB等类定义为Façade类的嵌套类。采取那样的不二等秘书诀,有利于这个独特的服务情势,被远程客户更有利于的调用。

图片 2
图二  错误的用例图

概念一个Service,最广泛的要么显式地将接口定义为Service。那样的艺术使得劳动的定义更灵活,那或多或少,小编已在Part
Two中有过描述。当然,选择这种方法,就不存在前面所述的村办方法成为服务章程的花样了,因为在多个接口定义中,全体办法都以public的。

   
鲜明了以“服务”为基本的程序结构,大家才能够更加好地接纳WCF,定制自个儿的服务,分掌握服务的界线,定义好音信的格式。即使,3个聊天室程序不可能反映SOA的中坚精神,可是树立面向服务的构思真正要求的。正如笔者辈在始发面向对象程序设计时,须求建立面向对象的想想同样。

除此以外1个话题是有关“服务接口的接续”。二个被标记了[ServiceContract]的接口,在其继承链上,允许全数多少个一样标记了[ServiceContract]的接口。对接口钦点义的OperationContract方法,则是基于“聚合”的尺度,如下的代码所示:
[ServiceContract]
public interface IOne
{
    [OperationContract(IsOneWay=true)]
    void A();
}
[ServiceContract]
public interface ITwo
{
    [OperationContract]
    void B();
}
[ServiceContract]
public interface IOneTwo : IOne, ITwo
{
    [OperationContract]
    void C();
}

   
该聊天室程序的贯彻主要通过Duplex来完结,个中又选取了MulticastDelegate与异步调用。当中,服务接口的定义如下:
    [ServiceContract(SessionMode = SessionMode.Required,
CallbackContract = typeof(IChatCallback))]
    interface IChat
    {
        [OperationContract(IsOneWay = false, IsInitiating = true,
IsTerminating = false)]
        string[] Join(string name);

在那些事例中,接口IOneTwo继承了接口IOne和ITwo。此时服务IOneTwo暴光的服务章程应该为方法A、B和C。

        [OperationContract(IsOneWay = true, IsInitiating = false,
IsTerminating = false)]
        void Say(string msg);

不过当大家应用Duplex音信交流情势(文章后边会详细介绍Duplex)时,对于服务接口的回调接口在接口继承上有一定的限制。WCF须要服务接口IB在此起彼伏另二个劳务接口IA时,IB的回调接口IBCallBack必须同时继续IACallBack,不然会抛出InvalidContractException万分。正确的概念如下所示:
[ServiceContract(CallbackContract = IACallback)]
interface IA {}
interface IACallback {}

        [OperationContract(IsOneWay = true, IsInitiating = false,
IsTerminating = false)]
        void Whisper(string to, string msg);

[ServiceContract(CallbackContract = IBCallback)]
interface IB : IA {}
interface IBCallback : IACallback {}

        [OperationContract(IsOneWay = true, IsInitiating = false,
IsTerminating = true)]
        void Leave();
}

伍、音讯交流形式(Message Exchange Patterns,MEPS)
在WCF中,服务端与客户端之间新闻的置换共有三种情势:Request/Reply,One-Way,Duplex。

    回调接口的概念如下:
    interface IChatCallback
    {
        [OperationContract(IsOneWay = true)]
        void Receive(string senderName, string message);

1、Request/Reply
那是暗许的1种音讯调换格局,客户端调用服务章程发出请求(Request),服务端收到请求后,实行对应的操作,然后回来1个结实值(Reply)。

        [OperationContract(IsOneWay = true)]
        void ReceiveWhisper(string senderName, string message);

万壹未有其余尤其的装置,3个办法假若标记了OperationContract,则该措施的音讯交换方式正是运用的Request/Reply形式,即便它的再次回到值是void。当然,大家也得以将IsOneWay设置为false,那也是暗中认可的设置。如下的代码所示:
[ServiceContract]
public interface ICalculator
{
 [OperationContract]
 int Add(int a, int b);

        [OperationContract(IsOneWay = true)]
        void UserEnter(string name);

 [OperationContract]
 int Subtract(int a, int b);
}

        [OperationContract(IsOneWay = true)]
        void UserLeave(string name);
    }

2、One-Way
尽管音讯交流形式为One-Way,则注解客户端与服务端之间只有请求,未有响应。尽管响应音讯被发生,该响应音讯也会被忽视。那种办法接近于音讯的关照只怕广播。当三个服务方法被装置为One-Way时,假如该办法有再次来到值,会抛出InvalidOperationException很是。

   
服务提供了Join、Say、Whisper与Leave等接口方法,向对应的是回调接口的接口方法。在贯彻IChat服务接口的劳务类ChatService中,定义了委托Chat伊夫ntHandler与Chat伊夫ntHandler类型的轩然大波Chat伊夫nt,就是经过它实现了辨识了客户的新闻广播。方法如下:
     private void BroadcastMessage(ChatEventArgs e)
     {
         ChatEventHandler temp = ChatEvent;

要将服务章程设置为One-Way非凡简单,只供给将OperationContractAttribute的性质IsOneWay设置为true就能够了,如下的代码所示:
public class Radio
{
 [OperationContract(IsOneWay=true)]
 private void BroadCast();
}

         if (temp != null)
         {
             foreach (ChatEventHandler handler in
temp.GetInvocationList())
             {
                handler.BeginInvoke(this, e, new
AsyncCallback(EndAsync), null);
             }
         }
    }

3、Duplex
Duplex音信沟通情势抱有客户端与服务端双向通讯的成效,同时它的落实还是能使新闻沟通具有异步回调的职能。

   
在客户端参预聊天室程序在此之前,该客户端并不曾订阅Chat伊芙nt事件,此时调用布罗兹castMessage方法,在经过GetInvocationList方法取得MulticastDelegate时,不存在该客户端的委托实例。因此,别的客户在经过聊天室实行聊天时,不会将聊天音讯发送到该客户端。体现在先后中,正是Join方法的如下代码片断:
    myEventHandler = new ChatEventHandler(MyEventHandler);
    ……

要落实消息调换的Duplex,绝相比较复杂。它需求定义四个接口,在那之中服务接口用于客户端向服务端发送音信,而回调接口则是从服务端重返音讯给客户端,它是经过回调的诀窍来形成的。接口定义如下:
劳务接口:
[ServiceContract(Namespace =
http://microsoft.servicemodel.samples/“,
Session = true, CallbackContract=typeof(ICalculatorDuplexCallback))]
public interface ICalculatorDuplex
{
    [OperationContract(IsOneWay=true)]
    void Clear();

    callback =
OperationContext.Current.GetCallbackChannel<IChatCallback>();
    ChatEventArgs e = new ChatEventArgs();
    e.msgType = MessageType.UserEnter;
    e.name = name;
    BroadcastMessage(e);
    ChatEvent += myEventHandler;
    ……

    [OperationContract(IsOneWay = true)]
    void AddTo(double n);

    注意看,Chat伊芙nt +=
my伊夫ntHandler语句是身处布罗兹castMessage方法调用之后。一旦该客户端插足聊天室程序之后,再调用布罗兹castMessage方法,该客户端就能接受音讯了。

    [OperationContract(IsOneWay = true)]
    void SubtractFrom(double n);

   
Chat伊夫nt事件指向的艺术是My伊芙ntHandler,该情势将履行回调接口的有关措施:
    private void MyEventHandler(object sender, ChatEventArgs e)
    {
        try
        {
            switch (e.msgType)
            {
                case MessageType.Receive:
                    callback.Receive(e.name, e.message);
                    break;
                case MessageType.ReceiveWhisper:
                    callback.ReceiveWhisper(e.name, e.message);
                    break;
                case MessageType.UserEnter:
                    callback.UserEnter(e.name);
                    break;
                case MessageType.UserLeave:
                    callback.UserLeave(e.name);
                    break;
            }
        }
        catch
        {
            Leave();
        }
    }

    [OperationContract(IsOneWay = true)]
    void MultiplyBy(double n);

   
还供给小心的是Whisper方法。由于它达成了私聊功效,由此向钦定客户发送音讯时,不应当使用广播格局。怎么样找到钦点客户呢?那亟需四个Dictionary集合,保存客户名和与之相应的ChatEventHandler实例。在执行Whisper方法时,就能够依照客户名找到相应的Chat伊夫ntHandler实例进行调用:
    public void Whisper(string to, string msg)
    {
        ChatEventArgs e = new ChatEventArgs();
        e.msgType = MessageType.ReceiveWhisper;
        e.name = this.name;
        e.message = msg;
        try
        {
            ChatEventHandler chatterTo;
            lock (syncObj)
            {
                chatterTo = chatters[to];
            }
            chatterTo.BeginInvoke(this, e, new AsyncCallback(EndAsync),
null);
        }
        catch (KeyNotFoundException)
        {
        }
    }

    [OperationContract(IsOneWay = true)]
    void DivideBy(double n);
}
回调接口:
public interface ICalculatorDuplexCallback
{
    [OperationContract(IsOneWay = true)]
    void Equals(double result);

   
在客户端代码中,服务接口的调用选拔了异步调用的格局,例如客户端插足聊天室:
    proxy = new ChatProxy(site);
    IAsyncResult iar = proxy.BeginJoin(myNick, new
AsyncCallback(OnEndJoin), null);

    [OperationContract(IsOneWay = true)]
    void Equation(string equation);
}
小心在接口定义中,每种服务形式的消息转换情势均安装为One-Way。其它,回调接口是被本地调用,由此不需求定义[ServiceContract]。在劳动接口中,供给设置ServiceContractAttribute的CallbackContract属性,使其针对性回调接口的项目type。

   
运维聊天室程序时,服务端仅供给提供稳定而不断的服务。聊天的加入者均为客户端用户。由此服务端的运维代码如下所示:
    Uri uri = new Uri(ConfigurationManager.AppSettings[“addr”]);
    ServiceHost host = new ServiceHost(typeof(NikeSoftChat.ChatService),
uri);
    host.Open();
    Console.WriteLine(“Chat service listen on endpoint {0}”,
uri.ToString());
    Console.WriteLine(“Press ENTER to stop chat service…”);
    Console.ReadLine();
    host.Abort();
    host.Close();

对于落到实处劳务的类,实例化方式(InstanceContextMode)毕竟是行使PerSession格局,依然PerCall情势,应依据该服务目的是还是不是要求保留景况来控制。如果是PerSession,则服务对象的生命周期是水保于2个对话期间。而PerCall格局下,服务对象是在格局被调用时创设,甘休后即被销毁。但是在Duplex情势下,不可能动用Single格局,不然会促成非凡抛出。本例的落到实处如下:
[ServiceBehavior(InstanceContextMode =
InstanceContextMode.PerSession)]
public class CalculatorService : ICalculatorDuplex
{
    double result;
    string equation;
    ICalculatorDuplexCallback callback = null;
    public CalculatorService()
    {
        result = 0.0D;
        equation = result.ToString();
        callback = OperationContext.Current.
        GetCallbackChannel();
    }
    public void AddTo(double n)
    {
        result += n;
        equation += ” + ” + n.ToString();
        callback.Equals(result);
    }
   // Other code not shown.
}

    本文Sample的撰稿人是NikolaPaljetak。鉴于我自个儿在代码所附的批准表明,为了扶助大家阅读本文,在此附上NikolaPaljetak的萨姆ple,你可以在WCF官方网站中找到它。NikolaPaljetak的许可注明如下:
    Permission is granted to anyone to use this software for any
purpose, including commercial applications.

在类CalculatorService中,回调接口对象callback通过OperationContext.Current.GetCallbackChannel<>()获取。然后在服务方法例如AddTo()中,通过调用该回调对象的章程,完结服务端向客户端重返音信的功用。

在采用Duplex时,Contract使用的Binding应该是系统提供的WSDualHttpBinding,即使应用BasicHttpBinding,会产出错误。因而Host程序应该如下所示:
    public static void Main(string[] args)
    {
        Uri uri = new
Uri(“http://localhost:8080/servicemodelsamples“);
        using (ServiceHost host = new
ServiceHost(typeof(CalculatorService), uri))
        {
            host.AddServiceEndpoint(typeof(ICalculatorDuplex),new
WSDualHttpBinding(),”service.svc”);
            host.Open();
            Console.WriteLine(“Press any key to quit service.”);
            Console.ReadKey();
        }
    }
设借使利用安顿文件,也应作相应的改动,如本例:
  <system.serviceModel>
    <client>
      <endpoint name=””
               
address=”http://localhost:8080/servicemodelsamples/service.svc
                binding=”wsDualHttpBinding”
                bindingConfiguration=”DuplexBinding”
                contract=”ICalculatorDuplex” />
    </client>
    <bindings>
      <!– configure a binding that support duplex communication
–>
      <wsDualHttpBinding>
        <binding name=”DuplexBinding”
                
clientBaseAddress=”http://localhost:8000/myClient/“>
        </binding>
      </wsDualHttpBinding>
    </bindings>
  </system.serviceModel>

当服务端将音信回送到客户端后,对音讯的拍卖是由回调对象来拍卖的,所以回调对象的兑现应有是在客户端完结,如下所示的代码应该是在客户端中:
    public class CallbackHandler : ICalculatorDuplexCallback
    {
        public void Equals(double result)
        {
            Console.WriteLine(“Equals({0})”, result);
        }
        public void Equation(string equation)
        {
            Console.WriteLine(“Equation({0})”, equation);
        }
    }

客户端调用服务目的相应的为:
    class Client
    {
        static void Main()
        {
            // Construct InstanceContext to handle messages on
            // callback interface.
            InstanceContext site = new InstanceContext(new
CallbackHandler());

            // Create a proxy with given client endpoint
configuration.
            using (CalculatorDuplexProxy proxy =
            new CalculatorDuplexProxy(site, “default”))
            {
                double value = 100.00D;
                proxy.AddTo(value);
                value = 50.00D;
                proxy.SubtractFrom(value);
                // Other code not shown.

                // Wait for callback messages to complete before
                // closing.
                System.Threading.Thread.Sleep(500);
                // Close the proxy.
                proxy.Close();
            }
        }
    }

专注在Duplex中,会话创制的时机并不是客户端创立Proxy实例的时候,而是当服务对象的主意被第二次调用时,会话方才树立,此时劳动对象会在艺术调用在此以前被实例化,直至会话截止,服务对象都是存在的。

如上的代码例子在WinFX的SDK
萨姆ple中得以找到。但是该例子并不能够一贯呈现出Duplex效能。通过前面包车型大巴介绍,我们领会Duplex具有客户端与服务端双向通讯的意义,同时它的贯彻还是能使音讯沟通具有异步回调的机能。因而,作者分别完成了四个实例来呈现Duplex在两上边的功效。

(一)客户端与服务端双向通讯功用——ChatDuplexWin
实例证实:1个接近于聊天室的小程序。利用Duplex援助客户端与服务端通讯的特色,完成了客户端与服务端聊天的作用。

劳务接口和回调接口的概念如下:
    [ServiceContract(Namespace =
http://www.brucezhang.com/WCF/Samples/ChatDuplex“, Session = true,
CallbackContract=typeof(IChatDuplexCallback))]
    public interface IChatDuplex
    {
        [OperationContract(IsOneWay=true)]
        void Request(string cltMsg);
        [OperationContract(IsOneWay = true)]
        void Start();
    }
    public interface IChatDuplexCallback
    {
        [OperationContract(IsOneWay=true)]
        void Reply(string srvMsg);
    }
很备受关注,Request方法的功效为客户端向服务端发送消息,Reply方法则使服务端回送消息给客户端。服务接口IChatDuplex中的Start()方法,用于展现的成立2个对话,因为在那一个艺术中,作者急需直接获取callback对象,使得服务端不必等待客户端首发送消息,而是能够使用callback对象积极先向客户端发送音信,从而达成聊天功效。

兑现类的代码如下:
    [ServiceBehavior(InstanceContextMode =
InstanceContextMode.PerSession)]
    public class ChatDuplex:IChatDuplex
    {
        public ChatDuplex()
        {
            m_callback =
OperationContext.Current.GetCallbackChannel();           
        }
        private IChatDuplexCallback m_callback = null;           
        public void Request(string cltMsg)
        {
           
ChatRoomUtil.MainForm.FillListBox(string.Format(“Client:{0}”,
cltMsg));           
        }
        public void Start()
        {
            ChatRoomUtil.MainForm.SetIIMDuplexCallback(m_callback);
        }
    }

因为自身须求在劳动端界面中,能够将客户端发送来的新闻呈现在主窗体界面中。所以接纳了全局变量MainForm,用来保存主窗体对象:
    public static class ChatRoomUtil
    {
        public static ServerForm MainForm = new ServerForm();
    }
而在服务端程序运营时,Application运营的主窗口也为该全局变量:
Application.Run(ChatRoomUtil.MainForm);

要达成聊天成效,最大的阻碍是当服务端收到客户端音信时,无法立时Reply新闻,而应等待服务端用户输入回送的新闻内容,方得以Reply。也正是说,当客户端调用劳动对象的Request方法时,不能够直接调用callback对象。由此作者动用Start()方法,将劳动对象中获得的callback对象传递到主窗体对象中。那样,callback对象就足以留待服务端发送音讯时调用了:
    public partial class ServerForm : Form
    {       
        private IChatDuplexCallback m_callback;
        private void btnSend_Click(object sender, EventArgs e)
        {
            if (txtMessage.Text != string.Empty)
            {
                lbMessage.Items.Add(string.Format(“Server:{0}”,
txtMessage.Text));
                if (m_callback != null)
                {
                    m_callback.Reply(txtMessage.Text);
                }
                txtMessage.Text = string.Empty;
            }
        }       
        public void FillListBox(string message)
        {
            lbMessage.Items.Add(message);
        }
        public void SetIIMDuplexCallback(IChatDuplexCallback callback)
        {
            m_callback = callback;
        }
     //Other code not shown;
    }

对于客户端的兑现,相对简便易行,须求留意的是回调接口的贯彻:
    public class ChatDuplexCallbackHandler:IChatDuplexCallback
    {
        public ChatDuplexCallbackHandler(ListBox listBox)
        {
            m_listBox = listBox;
        }
        private ListBox m_listBox;       

        public void Reply(string srvMsg)
        {
            m_listBox.Items.Add(string.Format(“Server:{0}”, srvMsg));
        }
    }
由于自身自定义了该指标的构造函数,所以在实利化proxy时会有微微分歧:
InstanceContext site = new InstanceContext(new
ChatDuplexCallbackHandler(this.lbMessage));
proxy = new ChatDuplexProxy(site);
proxy.Start();

经过proxy对象的Start()方法,使得我们在创制proxy对象开首,就创办了对话,从而使得劳动指标被实例化,从而能够运转上面包车型地铁那行代码:
m_callback = OperationContext.Current.GetCallbackChannel(); 

相当于说,在proxy对象建立之后,服务端就早已收获callback对象了,这样就能够保障服务端能够先向客户端发送新闻而不会因为callback为null,导致错误的发出。

(二)新闻沟通的异步回调功效——AsyncDuplexWin
实例证实:本实例相比较不难,只是为了求证当回调对象被调用时,客户端是还是不是能够被异步运营。调用服务目的时,服务端会议及展览开八个增进运算。在运算未形成在此之前,客户端会执行展现累加数字的职分,当服务端运算甘休后,只要客户端程序的线程处于Sleep状态,该回调对象就会被调用,然后依照用户挑选是还是不是再持续运转剩下的天职。本例中服务端为控制台应用程序,客户端则为Windows应用程序。

事例中的接口定义很是简单,不再赘言,而得以实现类的代码如下:
    public class SumDuplex:ISumDuplex
    {
        public SumDuplex()
        {
            callback = OperationContext.Current.GetCallbackChannel();
        }
        private ISumDuplexCallback callback = null;

        #region ISumDuplex Members
        public void Sum(int seed)
        {
            int result = 0;
            for (int i = 1; i < = seed; i++)
            {
                Thread.Sleep(10);
                Console.WriteLine(“now at {0}”,i);
                result += i;               
            }
            callback.Equals(result);
        }
        #endregion
    }
很通晓,当客户端调用该服务指标时,会在服务端的控制台上打字与印刷出迭代值。

是因为客户端必要在callback调用时,甘休对当前职务的运维,所以须求用到四线程机制:
    public delegate void DoWorkDelegate();
    public partial class ClientForm : Form
    {
        public ClientForm()
        {
            InitializeComponent();
            InstanceContext site = new InstanceContext(new
SumDuplexCallbackHandler(this.lbMessage));
            proxy = new SumDuplexProxy(site);           
        }
        private SumDuplexProxy proxy;
        private Thread thread = null;
        private DoWorkDelegate del = null;       
        private int counter = 0;

        private void btnStart_Click(object sender, EventArgs e)
        {
            proxy.Sum(100);
            thread = new Thread(new ThreadStart(delegate()
            {
                while (true)
                {
                    if (ClientUtil.IsCompleted)
                    {
                        if (MessageBox.Show(“Game over,Exit?”, “Notify”,
MessageBoxButtons.YesNo,
                            MessageBoxIcon.Question) ==
DialogResult.Yes)
                        {
                            break;
                        }
                    }
      if (counter > 10000)
                    {
                        break;
                    }
                    if (del != null)
                    {
                        del();
                    }
                    Thread.Sleep(50);
                }
            }
            ));
            del += new DoWorkDelegate(DoWork);
            thread.Start();
        }                  

        private void DoWork()
        {
            if (lbMessage.InvokeRequired)
            {
                this.Invoke(new DoWorkDelegate(DoWork));
            }
            else
            {
                lbMessage.Items.Add(counter);               
                lbMessage.Refresh();
                counter++;
            }
        }
        private void ClientForm_FormClosing(object sender,
FormClosingEventArgs e)
        {
            if (thread != null)
            {
                thread.Abort();
            }
        }
    }

因为急需在四线程中对ListBox控件的items举办修改,由于该控件不是线程安全的,所以应采用该控件的InvokeRequired属性。别的,在线程运转时的匿名情势中,利用while(true)控制当前线程的周转,并选取全局变量ClientUtil.IsCompleted判断回调对象是或不是被调用,要是被调用了,则会弹出对话框,选用是或不是退出当前职分。那里所谓的当前任务实际上正是调用DoWork方法,向ListBox控件的items中连连添加累加的counter值。注意客户端的回调对象完结如下:
    class SumDuplexCallbackHandler:ISumDuplexCallback
    {
        public SumDuplexCallbackHandler(ListBox listBox)
        {
            m_listBox = listBox;
        }
        private ListBox m_listBox;      
        #region ISumDuplexCallback Members
        public void Equals(int result)
        {
     ClientUtil.IsCompleted = true;
            m_listBox.Items.Add(string.Format(“The result is:{0}”,
result));
            m_listBox.Refresh();           
        }     
        #endregion
    }
当客户端点击Start按钮,调用服务对象的Sum方法后,在劳务端会彰显迭代值,而客户端也早先履行自个儿的任务,向List博克斯控件中添加累加数。一旦服务端运算完成,就将运算结果通过回调对象传递到客户端,全局变量ClientUtil.IsCompleted将被安装为true。要是添加累加值的线程处于sleep状态,系统就会将结果值添加到ListBox控件中,同时会弹出对话框,决定是或不是继续剩下的职务。

注:本文示例的代码和实例均在Feb 200陆 CTP版本下运营。

< 未完待续>

参考:
1、David Chappell,Introducing Windows Communication Foundation
2、Microsoft Corporation,WinFX SDK