和XML Web服务异步地通讯
  和一个XML Web服务异步通讯遵循被Microsoft.NET Framework其它部分使用的异步设计模式。然而,在你取得那些细节之前,重要的是注意一个XML Web服务不必特意的写来处理用于异步调用的异步请求。你使用Wsdl.exe为你的客户端创建的代理类自动地创建用于异步调用XML Web服务方法的方法。即使只有一个XML Web服务方法的同步实现也是这样的。
  .NET Framework异步方法调用设计模式
  用于调用异步方法的设计模式,尤其是用于.NET Framework,针对每个同步方法分别有两个异步方法。对每个同步方法,都有一个Begin异步方法和一个End异步方法。Begin方法被客户端调用来开始方法调用。也就是说,客户端指示这个方法来开始处理方法调用,但是立即返回。End方法被客户端调用来取得XML Web服务方法调用执行的处理结果。
  一个客户端如何知道何时调用End方法?.NET Framework定义了两种方法来实现客户端判断其时间。第一种是传送一个回调函数到Begin方法,当方法已经完成处理的时候调用。第二个方法是使用WaitHandle类的一个方法来导致客户端等待方法完成。当一个客户端实现第二个方法,并且调用Begin方法,返回值不是XML Web服务方法指定的数据类型,而是一个实现IAsyncResult接口的类型。IAsyncResult接口包含一个WaitHandle类型的AsyncWaitHandle属性,实现支持等待同步对象变为带有WaitHandle.WaitOne、WaitAny和WaitAll标记的方法。当一个同步对象被标记的时候,它指示等待特定的资源的线程可以访问资源的。如果一个XML Web服务客户端使用wait方法仅仅异步地调用一个XML Web服务方法,那么它可以调用WaitOne来等待XML Web服务方法完成处理。
  重要的是注意不管客户端选择来与XML Web服务异步通讯的两种方法中的哪一种,SOAP消息发送和接收都与同步通信时吻合。也就是说,只有一个SOAP请求和SOAP响应通过网络发送和接收。代理类通过使用一个不同的线程而不是客户端用来调用Begin方法的线程来处理SOAP响应。因此,客户端可以继续执行线程上的其它的工作,而代理类处理接收和操作SOAP响应。
  实现一个产生异步的方法调用的XML Web服务客户端
  用于从使用ASP.NET创建的XML Web服务客户端产生一个到XML Web服务的异步调用的体系结构被嵌入.NET Framework和由Wsdl.exe构造的代理类中。用于异步调用的设计模式被.NET Framework定义,代理类提供和一个XML Web服务异步通信的机制。当一个用于XML Web服务的代理类被使用Wsdl.exe构造的时候,有三个方法分别被创建,用于XML Web服务中的公共XML Web服务方法。下面的表格描述那三个方法。
  代理类中的方法名 描述
  <NameOfWebServiceMethod> 同步发送用于名为<NameOfWebServiceMethod>的XML Web服务方法的消息。
  Begin<NameOfWebServiceMethod> 开始与名为<NameOfWebServiceMethod>的XML Web服务方法的异步消息通信。
  End<NameOfWebServiceMethod> 结束与名为<NameOfWebServiceMethod>的XML Web服务方法的异步消息通信,从XML Web服务方法中取得完成的消息。
  下面的代码示例是一个XML Web服务方法,它可能花费相对长的时间来完成处理。因此,当你应该设置你的XML Web服务客户端来异步地调用XML Web服务方法的时候,它是一个很好的示例。
| [C#]<%@ WebService Language="C#" Class="PrimeFactorizer" %>
 
 using System;
 using System.Collections;
 using System.Web.Services;
 
 class PrimeFactorizer {
 
 [WebMethod]
 public long[] Factorize(long factorizableNum){
 ArrayList outList = new ArrayList();
 long i = 0;
 int j;
 try{
 long Check = factorizableNum;
 
 //Go through every possible integer
 //factor between 2 and factorizableNum / 2.
 //Thus, for 21, check between 2 and 10.
 for (i = 2; i < (factorizableNum / 2); i++){
 while(Check % i == 0){
 outList.Add(i);
 Check = (Check/i);
 }
 }
 //Double-check to see how many prime factors have been added.
 //If none, add 1 and the number.
 j = outList.Count;
 if (j == 0) {
 outList.Add(1);
 outList.Add(factorizableNum);
 }
 j = outList.Count;
 
 //Return the results and
 //create an array to hold them.
 long[] primeFactor = new long[j];
 for (j = 0; j < outList.Count; j++){
 //Pass the values one by one, making sure
 //to convert them to type ulong.
 primeFactor[j] = Convert.ToInt64(outList[j]);
 }
 return primeFactor;
 }
 catch (Exception) {
 return null;
 }
 }
 }
 
 [Visual Basic]
 <%@ WebService Class="PrimeFactorizer" Language="VB" %>
 Imports System
 Imports System.Collections
 Imports System.Web.Services
 
 Public Class PrimeFactorizer
 <WebMethod> _
 Public Function Factorize(factorizableNum As Long) As Long()
 Dim outList As New ArrayList()
 Dim i As Long = 0
 Dim j As Integer
 Try
 Dim Check As Long = factorizableNum
 
 'Go through every possible integer
 'factor between 2 and factorizableNum / 2.
 'Thus, for 21, check between 2 and 10.
 For i = 2 To CLng(factorizableNum / 2) - 1
 While Check Mod i = 0
 outList.Add(i)
 Check = CLng(Check / i)
 End While
 Next i
 'Double-check to see how many prime factors have been added.
 'If none, add 1 and the number.
 j = outList.Count
 If j = 0 Then
 outList.Add(1)
 outList.Add(factorizableNum)
 End If
 j = outList.Count
 
 'Return the results and
 'create an array to hold them.
 Dim primeFactor(j - 1) As Long
 For j = 0 To outList.Count - 1
 'Pass the values one by one, making sure
 'to convert them to type ulong.
 primeFactor(j) = CLng(outList(j))
 Next j
 Return primeFactor
 Catch
 Return Nothing
 End Try
 End Function
 End Class
 | 
  下面的代码示例是一个Wsdl.exe生成的代理类的一部分,用于上述XML Web服务方法。注意BeginFactorize和EndFactorize方法,因为它们被用来与Factorize XML Web服务方法异步通信。
| public class PrimeFactorizer : System.Web.Services.Protocols.SoapHttpClientProtocol {
 public long[] Factorize(long factorizableNum) {
 object[] results = this.Invoke("Factorize", new object[] { factorizableNum});
 return ((long[])(results[0]));
 }
 
 public System.IAsyncResult BeginFactorize(long factorizableNum, System.AsyncCallback callback, object  asyncState) {
 return this.BeginInvoke("Factorize", new object[] {
 factorizableNum}, callback, asyncState);
 }
 
 public long[] EndFactorize(System.IAsyncResult asyncResult) {
 object[] results = this.EndInvoke(asyncResult);
 return ((long[])(results[0]));
 }
 }
 | 
  有两个方法用来和XML Web服务方法异步通信。下面的代码示例说明了如何与一个XML Web服务方法异步通信,并且使用回调函数来取得XML Web服务方法的结果。
| [C#]using System;
 using System.Runtime.Remoting.Messaging;
 using MyFactorize;
 
 class TestCallback
 {
 public static void Main(){
 long factorizableNum = 12345;
 PrimeFactorizer pf = new PrimeFactorizer();
 
 //Instantiate an AsyncCallback delegate to use as a parameter
 //in the BeginFactorize method.
 AsyncCallback cb = new AsyncCallback(TestCallback.FactorizeCallback);
 
 // Begin the Async call to Factorize, passing in our
 // AsyncCalback delegate and a reference
 // to our instance of PrimeFactorizer.
 IAsyncResult ar = pf.BeginFactorize(factorizableNum, cb, pf);
 
 // Keep track of the time it takes to complete the async call
 // as the call proceeds.
 int start = DateTime.Now.Second;
 int currentSecond = start;
 while (ar.IsCompleted == false){
 if (currentSecond < DateTime.Now.Second) {
 currentSecond = DateTime.Now.Second;
 Console.WriteLine("Seconds Elapsed..." + (currentSecond - start).ToString() );
 }
 }
 // Once the call has completed, you need a method to ensure the
 // thread executing this Main function
 // doesn't complete prior to the call-back function completing.
 Console.Write("Press Enter to quit");
 int quitchar = Console.Read();
 }
 // Set up a call-back function that is invoked by the proxy class
 // when the asynchronous operation completes.
 public static void FactorizeCallback(IAsyncResult ar)
 {
 // You passed in our instance of PrimeFactorizer in the third
 // parameter to BeginFactorize, which is accessible in the
 // AsyncState property.
 PrimeFactorizer pf = (PrimeFactorizer) ar.AsyncState;
 long[] results;
 
 // Get the completed results.
 results = pf.EndFactorize(ar);
 
 //Output the results.
 Console.Write("12345 factors into: ");
 int j;
 for (j = 0; j<results.Length;j++){
 if (j == results.Length - 1)
 Console.WriteLine(results[j]);
 else
 Console.Write(results[j] + ", ");
 }
 }
 }
 
 [Visual Basic]
 Imports System
 Imports System.Runtime.Remoting.Messaging
 Imports MyFactorize
 
 Public Class TestCallback
 Public Shared Sub Main()
 Dim factorizableNum As Long = 12345
 Dim pf As PrimeFactorizer = new PrimeFactorizer()
 
 'Instantiate an AsyncCallback delegate to use as a parameter
 ' in the BeginFactorize method.
 Dim cb as AsyncCallback
 cb = new AsyncCallback(AddressOf TestCallback.FactorizeCallback)
 
 ' Begin the Async call to Factorize, passing in the
 ' AsyncCallback delegate and a reference to our instance
 ' of PrimeFactorizer.
 Dim ar As IAsyncResult = pf.BeginFactorize(factorizableNum, cb, pf)
 
 ' Keep track of the time it takes to complete the async call as
 ' the call proceeds.
 Dim start As Integer = DateTime.Now.Second
 Dim currentSecond As Integer = start
 Do while (ar.IsCompleted = false)
 If (currentSecond < DateTime.Now.Second) Then
 currentSecond = DateTime.Now.Second
 Console.WriteLine("Seconds Elapsed..." + (currentSecond - start).ToString() )
 End If
 Loop
 
 ' Once the call has completed, you need a method to ensure the
 ' thread executing this Main function
 ' doesn't complete prior to the callback function completing.
 Console.Write("Press Enter to quit")
 Dim quitchar As Integer = Console.Read()
 End Sub
 
 ' Set up the call-back function that is invoked by the proxy
 ' class when the asynchronous operation completes.
 Public Shared Sub FactorizeCallback(ar As IAsyncResult)
 
 ' You passed in the instance of PrimeFactorizer in the third
 ' parameter to BeginFactorize, which is accessible in the
 ' AsyncState property.
 
 Dim pf As PrimeFactorizer = ar.AsyncState
 Dim results() as Long
 
 ' Get the completed results.
 results = pf.EndFactorize(ar)
 
 'Output the results.
 Console.Write("12345 factors into: ")
 Dim j as Integer
 For j = 0 To results.Length - 1
 If j = (results.Length - 1) Then
 Console.WriteLine(results(j) )
 Else
 Console.Write(results(j).ToString + ", ")
 End If
 Next j
 End Sub
 End Class
 | 
  下面的代码示例说明了如何与一个XML Web服务方法异步通信,然后使用一个同步对象来等待处理结束。
| [C#]// -----------------------------------------------------------------------// Async Variation 2.
 // Asynchronously invoke the Factorize method,
 //without specifying a call back.
 using System;
 using System.Runtime.Remoting.Messaging;
 // MyFactorize, is the name of the namespace in which the proxy class is
 // a member of for this sample.
 using MyFactorize;
 
 class TestCallback
 {
 public static void Main(){
 long factorizableNum = 12345;
 PrimeFactorizer pf = new PrimeFactorizer();
 
 // Begin the Async call to Factorize.
 IAsyncResult ar = pf.BeginFactorize(factorizableNum, null, null);
 
 // Wait for the asynchronous operation to complete.
 ar.AsyncWaitHandle.WaitOne();
 
 // Get the completed results.
 long[] results;
 results = pf.EndFactorize(ar);
 
 //Output the results.
 Console.Write("12345 factors into: ");
 int j;
 for (j = 0; j<results.Length;j++){
 if (j == results.Length - 1)
 Console.WriteLine(results[j]);
 else
 Console.Write(results[j] + ", ");
 }
 }
 }
 
 [Visual Basic]
 Imports System
 Imports System.Runtime.Remoting.Messaging
 Imports MyFactorize ' Proxy class namespace
 
 Public Class TestCallback
 Public Shared Sub Main()
 Dim factorizableNum As Long = 12345
 Dim pf As PrimeFactorizer = new PrimeFactorizer()
 
 ' Begin the Async call to Factorize.
 Dim ar As IAsyncResult = pf.BeginFactorize(factorizableNum, Nothing, Nothing)
 
 ' Wait for the asynchronous operation to complete.
 ar.AsyncWaitHandle.WaitOne()
 
 ' Get the completed results.
 Dim results() as Long
 results = pf.EndFactorize(ar)
 
 'Output the results.
 Console.Write("12345 factors into: ")
 Dim j as Integer
 For j = 0 To results.Length - 1
 If j = (results.Length - 1) Then
 Console.WriteLine(results(j) )
 Else
 Console.Write(results(j).ToString + ", ")
 End If
 Next j
 End Sub
 End Class
 | 
  注意:如果FactorizeCallback是一个需要同步化/线成亲和上下文的上下文绑定类,那么回调被通过上下文分配体系结构来分配。换句话说,相对于它的对这样的上下文的调用者,回调可能异步的执行。在方法标记上有单向修饰词的精确的语义。这指的是任何这样的方法调用可能同步地或异步地执行,相对于调用者,并且在执行控制返回给它的时候,调用者不能产生任何关于完成这样一个调用的假设。
而且,在异步操作完成之前调用EndInvoke将阻塞调用者。使用相同的AsyncResult再次调用它的行为是不确定的。
  Cancel方法是一个在过去一段特定时间之后取消方法处理的请求。注意它是一个客户端的请求,并且最好服务器对此有所承诺。在接收到方法已经被取消的消息之后,客户端就不必做服务器是否已经停止处理的假设了。客户端最好不要破坏资源,例如文件对象,因为服务器可能仍然需要使用它们。IAsyncResult实例的IsCompleted属性在服务器结束它的处理之后将被设置为true,不再使用任何客户端提供的资源。因此,IsCompleted属性设置为true之后,客户端就可以安全的销毁资源了。