深入学习Web Service系列之
				异步开发模式
				
						
						
						
				
		
		
				——《深入学习
				Web Service
				系列》之一
		
		
				Terrylee
				,
				2005
				年
				12
				月
				4
				日
		
		
				
						概述
				
		
		
				在本篇随笔中,通过一些简单的示例来说一下
				Web Service
				中的异步调用模式。调用
				Web Service
				方法有两种方式,同步调用和异步调用。同步调用是程序继续执行前等候调用的完成,而异步调用在后台继续时,程序也继续执行,不必等待方法处理完成而直接返回。具体的调用流程见下图:
		
		
				
						 
				
		
		
				对于同步调用方法而言,
				UI
				线程依赖于方法的实现,方法执行时间过长将导致
				UI
				无法及时与用户进行交互。我们知道,在
				Windows
				客户端中,每个进程都有单一的
				UI
				进程,在服务器中,可扩展性依赖于线程的使用。对于异步调用方法而言,能够及时于用户交互响应,从而提供了良好的用户体验;同时也可以改善服务器的可扩展性,将服务器与通讯问题隔离。
		
		
				
						
						
				
		
		
				
						客户端异步调用方法
				
				
						
								
								
						
				
		
		
				在客户端异步调用是完全基于
				Proxy
				的方法,异步行为最简单的模式。
				Visual Studio
				和
				WSDL.EXE
				提供对它的直接支持。所以我们不必在
				Web
				服务应用程序中编写额外的代码来处理异步调用。
		
		
				遍及
				.NET Framework
				的异步调用有一个基础的设计模式:
				Begin
				方法和
				End
				方法,他们分别用于开始和终止异步处理。
				Visual Studio
				和
				WSDL.exe
				生成了这两种方法:
		
		
				Begin<WebServiceMethodName>
				——该方法通知
				Web
				服务开始处理调用,并立即返回。该方法不返回
				Web
				服务调用所指定的数据类型,而是返回一种实现
				IasyncResult
				接口的数据类型。
		
		
				End<WebServiceMethodName>
				——该方法通知
				Web
				服务返回先前启动的
				Web
				方法所生成的结果。
		
		
				IasyncResult
				接口包含了
				WaitHandle
				类型的
				AsyncWaitHandle
				特性。这个公共接口允许用户的客户应用程序等待调用,而且,该接口将用
				Any
				或
				All
				语义(例如
				WaitHandle.WaitOne
				,
				WaitAny
				和
				WaitAll
				)作为信号通知客户应用程序。例如,如果想要客户应用程序异步等候一个
				Web
				方法,可调用
				WaitOne
				来处理要完成的
				Web
				服务。
		
		
				一般来说,客户端异步代理方法有两种实现机制:使用同步对象和回调机制(也许你可能对用这个词不习惯,实在找不到第二个词来代替,暂且这样称呼吧)
		
		
				
						同步对象
						
								
								
						
				
		
		
				同步对象允许用户对
				Web
				服务的方法进行调用(使用
				Begin
				方法),然后继续处理。在后面的程序中,可以调用
				End
				方法,传递同步对象,以便得到调用结果。这种方式下,能够继续执行函数中的程序流程,而不执行回调处理。在这里
				调用
				WaitOne()
				方法会挂起当前线程,避免忙等待的发生,直到
				Web Services
				方法调用结束返回后,该线程才会被重新唤起。
		
		
				示例代码:
		
		
				
						
						
				
		
		
				 1
				
				
				/**/
				
						///
						 
						<summary>
						
								
						
						 2
						
								
						
						///
						 利用同步对象实现异步调用
						 3
						
								
						
						///
						 
						</summary>
						
								
						
						 4
						
								
						
						///
						 
						<param name="sender"></param>
						
								
						
						 5
						
								
						
						///
						 
						<param name="e"></param>
						
						
				
				
				 6
				
				private
				 
				void
				 btn_AsyncClient_Click(
				object
				 sender, System.EventArgs e)
				 7
				
						
						
				
				
						
				
				
						{
						 8
						
								
    IAsyncResult ar 
						=
						 wsc.BeginHello(
						this
						.txt_UserName.Text, 
						null
						, 
						null
						);
						 9
						
								
								
						
						10
						
								
								
						
						11
						
								
    MessageBox.Show(
						"
						Continue to do some other things
						"
						);
						12
						
								
								
						
						13
						
								
    ar.AsyncWaitHandle.WaitOne();
						14
						
								
								
						
						15
						
								
    strHello 
						=
						 wsc.EndHello(ar);
						16
						
								
								
						
						17
						
								
    
						this
						.rtb_Result.Text 
						=
						 strHello;
						18
						
								
}
				
		 
		
		
		
				
						回调机制
						
								
								
						
				
		
		
				从本质上说,异步回调机制是委托的
				.NET
				等价物,它通过在异步操作完成时建立一个被调用的单独方法来进行工作。调用应用程序能够继续处理其他的任务,直到回调函数被调用为止。这就意味着处理已经完成了,应用程序可以正常运行了。使用同步对象不同于回调机制的区别是,当检查
				Web
				方法是否已经完成,以及检查
				Web
				方法中是否含有需要的结果时,我们无法对其进行控制,而在回调的情况中,
				Web
				方法一旦完成,这些工作就会被自动执行。
		
		
				示例代码:
		
		
				
						
						
				
		
		
				 1
				
				
				/**/
				
						///
						 
						<summary>
						
								
						
						 2
						
								
						
						///
						 显示结果
						 3
						
								
						
						///
						 
						</summary>
						
								
						
						 4
						
								
						
						///
						 
						<param name="sender"></param>
						
								
						
						 5
						
								
						
						///
						 
						<param name="e"></param>
						
						
				
				
				 6
				
				public
				 
				void
				 UpdateResult(
				object
				 sender, EventArgs e)
				 7
				
						
						
				
				
						
				
				
						{
						 8
						
								
    
						this
						.rtb_Result.Text 
						=
						 strHello;
						 9
						
								
}
				
				
						
				
				10
				
						
						
				
				11
				
						
				
				public
				 
				void
				 OnHelloComplete(IAsyncResult ar)
				12
				
						
						
				
				
						
				
				
						{
						13
						
								
    strHello 
						=
						 wsc.EndHello(ar);
						14
						
								
								
						
						15
						
								
    
						this
						.rtb_Result.Invoke(
						new
						 EventHandler(UpdateResult));
						16
						
								
}
				
				
						
				
				17
				
						
						
				
				18
				
						
						
				
				/**/
				
						///
						 
						<summary>
						
								
						
						19
						
								
						
						///
						 用回调机制实现异步调用
						20
						
								
						
						///
						 
						</summary>
						
								
						
						21
						
								
						
						///
						 
						<param name="sender"></param>
						
								
						
						22
						
								
						
						///
						 
						<param name="e"></param>
						
						
				
				
				23
				
				private
				 
				void
				 btn_CallBack_Click(
				object
				 sender, System.EventArgs e)
				24
				
						
						
				
				
						
				
				
						{
						25
						
								
    AsyncCallback cb 
						=
						 
						new
						 AsyncCallback(OnHelloComplete);
						26
						
								
								
						
						27
						
								
    wsc.BeginHello(
						this
						.txt_UserName.Text, cb, 
						null
						);
						28
						
								
}
				
		 
		
		
		
				使用回调机制还是同步对象取决于用户所面临的具体情况。在检查异步调用是否完成时,如果愿意对处理过程进行控制,那么可以选择使用同步对象。如果觉得自己编写代码来完成对
				Web
				服务的调用,且当方法一旦执行完毕就立即由所调用的特殊函数来处理所返回的结果更适合一些,那么就更适合用回调机制。
		
		
				
						
						
				
		
		
				在客户端使用异步方法调用,可以改进
				UI
				响应度,在服务器端不需要实现异步操作,对服务器来说是透明的,而且客户端能够在任何时间选择阻塞。
		
		
				
						
						
				
		
		
				
						服务端使用
				
				
						Soap One-Way
				
				
						方法
				
				
						
								
								
						
				
		
		
				在服务器端使用
				One-Way
				方法实现异步调用,其实质是将单项消息发送到端点。这种方式的特点是方法没有返回值,客户端方法不会从调用的服务器端方法中收到返回值;我们无法判断方法结束的时间,对于结果需要显式通知或者轮询。
		
		
				
						
						
				
		
		
				在
				Web 
				服务端,我们使用
				[SoapDocumentMethod]
				定义
				One-Way
				方法:
				
				
		
		
				
				 [System.Web.Services.Protocols.SoapDocumentMethod(OneWay
				=
				true
				)]
		 
		
				示例代码:
		
		
				
						
						
				
		
		
				 1
				
				
				/**/
				
						///
						 
						<summary>
						
								
						
						 2
						
								
						
						///
						 One-Way方式的异步调用Set
						 3
						
								
						
						///
						 
						</summary>
						
								
						
						 4
						
								
						
						///
						 
						<param name="sender"></param>
						
								
						
						 5
						
								
						
						///
						 
						<param name="e"></param>
						
						
				
				
				 6
				
				private
				 
				void
				 btn_OneWay_Click(
				object
				 sender, System.EventArgs e)
				 7
				
						
						
				
				
						
				
				
						{
						 8
						
								
    wsc.SetHello(
						this
						.txt_UserName.Text);    
						 9
						
								
}
				
				
						
				
				10
				
						
						
				
				11
				
						
						
				
				/**/
				
						///
						 
						<summary>
						
								
						
						12
						
								
						
						///
						 One-Way方式的异步调用Get
						13
						
								
						
						///
						 
						</summary>
						
								
						
						14
						
								
						
						///
						 
						<param name="sender"></param>
						
								
						
						15
						
								
						
						///
						 
						<param name="e"></param>
						
						
				
				
				16
				
				private
				 
				void
				 btn_onewayGet_Click(
				object
				 sender, System.EventArgs e)
				17
				
						
						
				
				
						
				
				
						{
						18
						
								
    strHello 
						=
						 wsc.GetHello();
						19
						
								
								
						
						20
						
								
    
						this
						.rtb_Result.Text 
						=
						 strHello;
						21
						
								
}
				
		 
		
		
		
				One-Way
				方法不适合于下列情况:
		
		
				
						l         
				
				方法需要对结果轮询
		
		
				
						l         
				
				方法需要同步
		
		
				
						
						
				
		
		
				
						服务端使用
				
				
						WSE SoapSender
				
				
						和
				
				
						SoapRecevier
				
		
		
				在进行本部分内容之前,我们需要安装
				WSE2.0
				。
				WSE
				支持面向消息的编程,为我们提供了
				SoapSender
				和
				SoapReceiver
				基类,它能够支持发送和接收
				SoapEnvelopes
				,同时它也通过
				SoapClient
				和
				SoapService
				提供了更多的事务支持。
				SoapSender
				和
				SoapReceiver
				在客户端和服务端同时实现,客户端使用
				SoapSender
				发送消息,同时可选择使用
				SoapReceiver
				接收消息;服务端使用
				SoapReceiver
				接收消息,同时也可以选择使用
				SoapSender
				发送通知和回应。
		
		
				示例代码:
		
		
				
						 客户端:
				
		
		
				 1
				
				
				/**/
				
						///
						 
						<summary>
						
								
						
						 2
						
								
    
						///
						 自定义的消息接收类
						 3
						
								
    
						///
						 
						</summary>
						
						
				
				
				 4
				
				    
				public
				 
				class
				 MyReceiver: SoapReceiver
				 5
				
						
						
    
				
						
				
				
						{
						 6
						
								
        
						public
						 
						static
						 Form1 form;
						 7
						
								
        
						private
						 
						string
						 strBody;
						 8
						
								
								
						
						 9
						
								
        
						protected
						 
						override
						 
						void
						 Receive(SoapEnvelope envelope)
						10
						
								
								
        
						
								
						
						
								{
								11
								
										
            strBody 
								=
								 envelope.InnerText;
								12
								
										
										
								
								13
								
										
										
            
								/**/
								
										///
										注意:在进行此项之前,一定要把rtb_Result控件的属性设为Public
										
										
								
								
								14
								
								            form.rtb_Result.Invoke(
								new
								 EventHandler(UpdateBody));
								15
								
										
        }
						
						
								
						
						16
						
								
								
						
						17
						
								
        
						void
						 UpdateBody(
						object
						 sender, System.EventArgs e)
						18
						
								
								
        
						
								
						
						
								{
								19
								
										
            form.rtb_Result.Text 
								=
								 strBody;
								20
								
										
        }
						
						
								
						
						21
						
								
    }
				
		 
		
		
		
				1
				
				
				/**/
				
						///
						 
						<summary>
						
								
						
						2
						
								
						
						///
						 用WSE实现异步调用
						3
						
								
						
						///
						 
						</summary>
						
								
						
						4
						
								
						
						///
						 
						<param name="sender"></param>
						
								
						
						5
						
								
						
						///
						 
						<param name="e"></param>
						
						
				
				
				6
				
				private
				 
				void
				 button4_Click(
				object
				 sender, System.EventArgs e)
				7
				
						
						
				
				
						
				
				
						{
						8
						
								
    wsc.FireEvent();
						9
						
								
}
				
		 
		Web Service端:
		
				 1
				
				private
				 ArrayList Listeners
				 2
				
						
						
        
				
						
				
				
						{
						 3
						
								
            
						get
						
								
						
						 4
						
								
								
            
						
								
						
						
								{
								 5
								
										
                
								return
								 (ArrayList)Application[
								"
								Listeners
								"
								];
								 6
								
										
            }
						
						
								
						
						 7
						
								
        }
				
				
						
				
				 8
				
						
						
				
				 9
				
						
        [WebMethod]
				10
				
						
        
				public
				 
				void
				 AddListener(
				string
				 listener)
				11
				
						
						
        
				
						
				
				
						{
						12
						
								
            ArrayList alist 
						=
						 (ArrayList)Application[
						"
						Listeners
						"
						];
						13
						
								
								
						
						14
						
								
            
						if
						(alist 
						==
						 
						null
						)
						15
						
								
                alist 
						=
						 
						new
						 ArrayList();
						16
						
								
								
						
						17
						
								
            alist.Add(listener);
						18
						
								
								
						
						19
						
								
            Application[
						"
						Listeners
						"
						] 
						=
						 alist;
						20
						
								
								
						
						21
						
								
        }
				
				
						
				
				22
				
						
						
				
				23
				
						
        [WebMethod]
				24
				
						
        
				public
				 
				void
				 FireEvent()
				25
				
						
						
        
				
						
				
				
						{
						26
						
								
            
						int
						 i;
						27
						
								
								
						
						28
						
								
            
						for
						(i 
						=
						 
						0
						;i 
						<
						 
						this
						.Listeners.Count;i
						++
						)
						29
						
								
								
            
						
								
						
						
								{
								30
								
										
                SoapEnvelope envelope 
								=
								 
								new
								 SoapEnvelope();
								31
								
										
										
								
								32
								
										
                envelope.SetBodyObject(
								"
								Hello World!
								"
								);
								33
								
										
										
								
								34
								
										
                envelope.Context.Addressing.Action 
								=
								 
								new
								 Action((
								string
								)(
								this
								.Listeners[i]));
								35
								
										
										
								
								36
								
										
                envelope.Context.Addressing.ReplyTo 
								=
								 
								new
								 ReplyTo(
								new
								 System.Uri((
								string
								)(
								this
								.Listeners[i])));
								37
								
										
										
								
								38
								
										
                SoapSender peerProxy 
								=
								 
								new
								 SoapSender(
								new
								 System.Uri((
								string
								)(
								this
								.Listeners[i])));
								39
								
										
										
								
								40
								
										
                peerProxy.Send(envelope);
								41
								
										
            }
						
						
								
						
						42
						
								
        }
				
		 
		
				
						服务端使用
				
				
						WSE 
				
				
						自定义
				
				
						SoapMSMQ
				
				
						传输
				
				
						
								
								
						
				
		
		
				SoapMSMQ
				是一款开源软件,简化使用
				WSE
				进行
				MSMQ
				操作,下载地址:
		
		
				
						http://www.codeproject.com/useritems/SoapMSMQ.asp
				
		
		
				SoapMSMQ
				完全支持事务,具有如下特点:
		
		
				
						l         
				
				在事务中,请求要被同步初始化
		
		
				
						l         
				
				同步阶段排队请求,并且返回令牌
		
		
				
						l         
				
				异步阶段处理各个事务
		
		
				
						l         
				
				所有持有令牌的请求都保证会被处理,但可能会不成功
		
		
				
						l         
				
				支持向客户端发送通知
		
		
				对
				SoapMSMQ
				感兴趣的朋友可以下载下来后,做进一步的研究。
		
		
				
						
						
				
		
		
				
						总结
				
				
						
								
								
						
				
		
		
				异步方法调用改善了客户端的响应和用户体验,增加了服务端的可扩展性。当方法需要耗费大量的时间时,可以采用异步方式调用,提供系统并发处理的能力。对于异步方式的开发,我们可以有如上所述的广泛选择。
		
		
				
						
						
				
		
		
				示例程序界面:

下载地址:
		
		
				
						http://www.cnblogs.com/Files/Terrylee/AsyncDemo.rar
						
						
								
原文地址:http://terrylee.cnblogs.com/archive/2005/12/05/290845.html