共用方式為


與 XML Web Service 進行非同步通訊

與 XML Web Service 進行非同步的通訊方式,是依照 Microsoft .NET Framework 所使用的非同步設計模式。深入瞭解詳細資訊前,有一點很重要:您不用特地將 XML Web Service 撰寫成處理非同步要求,就能以非同步方式呼叫它。換句話說,您以 Web 服務描述語言工具 (Wsdl.exe) 為用戶端建立的 Proxy 類別會自動建立方法,以非同步方式呼叫 XML Web Service 方法。即使 XML Web Service 方法只有一個同步實作,也可以進行這個工作。

.NET Framework 非同步方法引動過程設計模式

.NET Framework指出,非同步呼叫方法設計模式中,每個同步方法有兩個非同步方法。每個同步方法包含 Begin 非同步方法和 End 非同步方法。Begin 方法是供用戶端呼叫以啟動方法呼叫。換言之,用戶端會指示方法開始處理方法呼叫,但是要馬上傳回。而 End 方法是供用戶端呼叫以取得執行 XML Web Service 方法呼叫的結果。

客戶如何知道何時呼叫 End 方法?有兩種實作用戶端的方法可用來判斷這個時間點,如 .NET Framework 所定義。第一種是將回呼函式 (Callback Function) 傳遞到 Begin 方法,接著在方法完成處理時會呼叫該方法。第二種是使用其中一個 WaitHandle 類別,讓客戶等待方法完成。當客戶實作第二種方法並呼叫 Begin 方法時,傳回值不是 XML Web Service 方法所指定的資料型別,而是實作 IAsyncResult 介面的型別。IAsyncResult 介面包含 WaitHandle 型別的 AsyncWaitHandle 屬性,實作支援等候同步物件變成以 WaitHandle.WaitOneWaitAnyWaitAll 信號通知的方法。同步物件被信號通知後,表示等待使用特定資源的執行緒可以存取資源。如果 XML Web Service 用戶端使用的等待方法僅以非同步呼叫一個 XML Web Service 方法,服務用戶端可以呼叫 WaitOne 以等待 XML Web Service 方法處理完畢。

有一點相當重要,不管用戶端從兩種方法中選用哪一個與 XML Web Service 進行非同步通訊,傳送和接收的 SOAP 訊息與同步通訊一樣。也就是說,網路上傳送和接收的只有一個 SOAP 要求和一個 SOAP 回應。Proxy 類別並非使用用戶端用來呼叫 Begin 方法的執行緒,而是使用不同的執行緒處理 SOAP 回應來完成這項工作。這樣用戶端就能繼續在其執行緒上執行其他工作,讓 Proxy 類別負責接收及處理 SOAP 回應。

實作進行非同步方法呼叫的 XML Web Service 用戶端

.NET Framework 和 Web 服務描述語言工具 (Wsdl.exe) 建置的 Proxy 類別中內建基礎架構,可從使用 ASP.NET 建立之 XML Web Service 用戶端以非同步方式呼叫 XML Web Service。.NET Framework 定義以非同步方式呼叫的設計模式,Proxy 類別則提供方法與 XML Web Service 方法以非同步方式通訊。當您使用 Wsdl.exe 建置 XML Web Service 的 Proxy 類別時,有三種方法可在 XML Web Service 建立公用 XML Web Service 方法。下列資料表說明這三種方法。

Proxy 類別中的方法名稱 說明
<NameOfWebServiceMethod> 指定同步傳送訊息給名為 <NameOfWebServiceMethod> 的 XML Web Service 方法。
Begin<NameOfWebServiceMethod> 指定與名為 <NameOfWebServiceMethod> 的 XML Web Service 方法開始進行非同步訊息通訊。
End<NameOfWebServiceMethod> 指定與名為 <NameOfWebServiceMethod> 的 XML Web Service 方法非同步訊息通訊結束,從 XML Web Service 方法取得完成的訊息。

下列程式碼範例的 XML Web Service 方法可能需要耗費相當長的時間才能完成處理。您可從這個例子瞭解,何時該設定 XML Web Service 用戶端以非同步方式呼叫 XML Web Service 方法。

<%@ 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 Service 方法產生的部分 Proxy 類別。請留意用來和 Factorize XML Web Service 方法進行非同步通訊的 BeginFactorizeEndFactorize

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 Service 方法進行非同步通訊的方法有兩種。下列程式碼範例示範與 XML Web Service 方法進行非同步通訊,並使用回呼函式從 XML Web Service 方法擷取結果。

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 Service 方法進行非同步通訊,然後使用同步物件等待處理結束。

// -----------------------------------------------------------------------// 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 為需要同步化/執行緒相似性內容的內容繫結類別,回呼程序會透過內容發送器基礎架構進行分派。也就是說,為了配合其此種內容的呼叫端,回呼可能會以非同步方式執行。這種情況完全符合方法簽名碼 (Signature) 上單向限定詞 (Qualifier) 的語意。意思是說,這類的方法呼叫都會根據呼叫端使用同步或非同步方式執行,執行控制項傳回給呼叫端時,呼叫端不可假設呼叫已經完成。

而且,非同步作業完成前就呼叫 EndInvoke 會封鎖呼叫端。使用同一個 AsyncResult 第二次呼叫 EndInvoke 的行為不會被定義。

Cancel 方法可在方法超過指定的逾時週期後,要求取消方法處理。請注意,這種要求是由用戶端發出,建議伺服器端不要加以阻止。當用戶端收到方法已經取消的訊息後,不能假設伺服器已經停止處理方法。建議用戶端不要終結檔案物件等資源,因為伺服器可能仍在使用它們。伺服器必須完成處理、不再使用用戶端提供的任何資源時,IAsyncResult 執行個體的 IsCompleted 屬性才會設定為 true。等到 IsCompleted 屬性設定為 true,用戶端就可以確實終結資源。

請參閱

建置 XML Web Service 用戶端 | 探索 XML Web Service | 建立 XML Web Service 的用戶端 | 瀏覽現有使用 ASP.NET 建立的 XML Web Service | 從瀏覽器存取 XML Web Service