.NET之非同步程式設計

iDotNetSpace發表於2010-01-05

同步方法和非同步方法的區別
同步方法呼叫在程式繼續執行之前需要等待同步方法執行完畢返回結果
非同步方法則在被呼叫之後立即返回以便程式在被呼叫方法完成其任務的同時執行其它操作

非同步程式設計概覽
.NET Framework 允許您非同步呼叫任何方法。定義與您需要呼叫的方法具有相同簽名的委託;公共語言執行庫將自動為該委託定義具有適當簽名

的 BeginInvoke 和 EndInvoke 方法。

BeginInvoke 方法用於啟動非同步呼叫。它與您需要非同步執行的方法具有相同的引數,只不過還有兩個額外的引數(將在稍後描述)。

BeginInvoke 立即返回,不等待非同步呼叫完成。
BeginInvoke 返回 IasyncResult,可用於監視呼叫進度。

EndInvoke 方法用於檢索非同步呼叫結果。呼叫 BeginInvoke 後可隨時呼叫 EndInvoke 方法;如果非同步呼叫未完成,EndInvoke 將一直阻塞到

非同步呼叫完成。EndInvoke 的引數包括您需要非同步執行的方法的 out 和 ref 引數(在 Visual Basic 中為 ByRef 和 ByRef)以及由

BeginInvoke 返回的 IAsyncResult。

四種使用 BeginInvoke 和 EndInvoke 進行非同步呼叫的常用方法。呼叫了 BeginInvoke 後,可以:

1.進行某些操作,然後呼叫 EndInvoke 一直阻塞到呼叫完成。
2.使用 IAsyncResult.AsyncWaitHandle 獲取 WaitHandle,使用它的 WaitOne 方法將執行一直阻塞到發出 WaitHandle 訊號,然後呼叫

EndInvoke。這裡主要是主程式等待非同步方法,等待非同步方法的結果。
3.輪詢由 BeginInvoke 返回的 IAsyncResult,IAsyncResult.IsCompeted確定非同步呼叫何時完成,然後呼叫 EndInvoke。此處理個人認為與
相同。
4.將用於回撥方法的委託傳遞給 BeginInvoke。該方法在非同步呼叫完成後在 ThreadPool 執行緒上執行,它可以呼叫 EndInvoke。這是在強制裝

換回撥函式裡面IAsyncResult.AsyncState(BeginInvoke方法的最後一個引數)成委託,然後用委託執行EndInvoke。
警告   始終在非同步呼叫完成後呼叫 EndInvoke。

以上有不理解的稍後可以再理解。

 

例子
1)先來個簡單的沒有回撥函式的非同步方法例子

請再執行程式的時候,仔細看註釋,對理解很有幫助。還有,若將註釋的中的兩個方法都同步,你會發現非同步執行的速度優越性。

 

using System;

 namespace ConsoleApplication1
 {
     class Class1
     {
         //宣告委託
         public delegate void AsyncEventHandler();
 
         //非同步方法
         void Event1()
        {
            Console.WriteLine("Event1 Start");
            System.Threading.Thread.Sleep(4000);
            Console.WriteLine("Event1 End");
        }

        // 同步方法
        void Event2()
        {
            Console.WriteLine("Event2 Start");
            int i=1;
            while(i<1000)
            {
                i=i+1;
                Console.WriteLine("Event2 "+i.ToString());
            }
            Console.WriteLine("Event2 End");
        }

        [STAThread]
        static void Main(string[] args)
        {
            long start=0;
            long end=0;
            Class1 c = new Class1();
            Console.WriteLine("ready");
            start=DateTime.Now.Ticks;

            //例項委託
            AsyncEventHandler asy = new AsyncEventHandler(c.Event1);
            //非同步呼叫開始,沒有回撥函式和AsyncState,都為null
            IAsyncResult ia = asy.BeginInvoke(null, null);
            //同步開始,
            c.Event2();
            //非同步結束,若沒有結束,一直阻塞到呼叫完成,在此返回該函式的return,若有返回值。

          
            asy.EndInvoke(ia);

            //都同步的情況。
            //c.Event1();
            //c.Event2();
          
            end =DateTime.Now.Ticks;
            Console.WriteLine("時間刻度差="+ Convert.ToString(end-start) );
            Console.ReadLine();
        }
    }
}


 


2)下面看有回撥函式的WebRequest和WebResponse的非同步操作。

using System;
using System.Net;
using System.Threading;
using System.Text;
using System.IO;


// RequestState 類用於通過
// 非同步呼叫傳遞資料
public class RequestState
{
    const int BUFFER_SIZE = 1024;
    public StringBuilder RequestData;
    public byte[] BufferRead;
    public HttpWebRequest Request;
    public Stream ResponseStream;
    // 建立適當編碼型別的解碼器
    public Decoder StreamDecode = Encoding.UTF8.GetDecoder();

    public RequestState()
    {
        BufferRead = new byte[BUFFER_SIZE];
        RequestData = new StringBuilder("");
        Request = null;
        ResponseStream = null;
    }
}

// ClientGetAsync 發出非同步請求
class ClientGetAsync
{
    public static ManualResetEvent allDone = new ManualResetEvent(false);
    const int BUFFER_SIZE = 1024;

    public static void Main(string[] args)
    {

        if (args.Length < 1)
        {
            showusage();
            return;
        }

        // 從命令列獲取 URI
        Uri HttpSite = new Uri(args[0]);

        // 建立請求物件
        HttpWebRequest wreq = (HttpWebRequest)WebRequest.Create(HttpSite);

        // 建立狀態物件
        RequestState rs = new RequestState();

        // 將請求新增到狀態,以便它可以被來回傳遞
        rs.Request = wreq;

        // 發出非同步請求
        IAsyncResult r = (IAsyncResult)wreq.BeginGetResponse(new AsyncCallback(RespCallback), rs);

        // 將 ManualResetEvent 設定為 Wait,
        // 以便在呼叫回撥前,應用程式不退出
        allDone.WaitOne();
    }

    public static void showusage()
    {
        Console.WriteLine("嘗試獲取 (GET) 一個 URL");
        Console.WriteLine("\r\n用法::");
        Console.WriteLine("ClientGetAsync URL");
        Console.WriteLine("示例::");
        Console.WriteLine("ClientGetAsync http://www.microsoft.com/net/");
    }

    private static void RespCallback(IAsyncResult ar)
    {
        // 從非同步結果獲取 RequestState 物件
        RequestState rs = (RequestState)ar.AsyncState;

        // 從 RequestState 獲取 HttpWebRequest
        HttpWebRequest req = rs.Request;

        // 呼叫 EndGetResponse 生成 HttpWebResponse 物件
        // 該物件來自上面發出的請求
        HttpWebResponse resp = (HttpWebResponse)req.EndGetResponse(ar);

        // 既然我們擁有了響應,就該從
        // 響應流開始讀取資料了
        Stream ResponseStream = resp.GetResponseStream();

        // 該讀取操作也使用非同步完成,所以我們
        // 將要以 RequestState 儲存流
        rs.ResponseStream = ResponseStream;

        // 請注意,rs.BufferRead 被傳入到 BeginRead。
        // 這是資料將被讀入的位置。
        IAsyncResult iarRead = ResponseStream.BeginRead(rs.BufferRead, 0, BUFFER_SIZE, new AsyncCallback(ReadCallBack), rs);
    }


    private static void ReadCallBack(IAsyncResult asyncResult)
    {
        // 從 asyncresult 獲取 RequestState 物件
        RequestState rs = (RequestState)asyncResult.AsyncState;

        // 取出在 RespCallback 中設定的 ResponseStream
        Stream responseStream = rs.ResponseStream;

        // 此時 rs.BufferRead 中應該有一些資料。
        // 讀取操作將告訴我們那裡是否有資料
        int read = responseStream.EndRead(asyncResult);

        if (read > 0)
        {
            // 準備 Char 陣列緩衝區,用於向 Unicode 轉換
            Char[] charBuffer = new Char[BUFFER_SIZE];

            // 將位元組流轉換為 Char 陣列,然後轉換為字串
            // len 顯示多少字元被轉換為 Unicode
            int len = rs.StreamDecode.GetChars(rs.BufferRead, 0, read, charBuffer, 0);
            String str = new String(charBuffer, 0, len);

            // 將最近讀取的資料追加到 RequestData stringbuilder 物件中,
            // 該物件包含在 RequestState 中
            rs.RequestData.Append(str);


            // 現在發出另一個非同步呼叫,讀取更多的資料
            // 請注意,將不斷呼叫此過程,直到
            // responseStream.EndRead 返回 -1
            IAsyncResult ar = responseStream.BeginRead(rs.BufferRead, 0, BUFFER_SIZE, new AsyncCallback(ReadCallBack), rs);
        }
        else
        {
            if (rs.RequestData.Length > 1)
            {
                // 所有資料都已被讀取,因此將其顯示到控制檯
                string strContent;
                strContent = rs.RequestData.ToString();
                Console.WriteLine(strContent);
            }

            // 關閉響應流
            responseStream.Close();

            // 設定 ManualResetEvent,以便主執行緒可以退出
            allDone.Set();
        }
        return;
    }
}

 


在這裡有回撥函式,且非同步回撥中又有非同步操作。

首先是非同步獲得ResponseStream,然後非同步讀取資料。

這個程式非常經典。從中可以學到很多東西的。我們來共同探討。

 

總結
上面說過,.net framework 可以非同步呼叫任何方法。所以非同步用處廣泛。

在.net framework 類庫中也有很多非同步呼叫的方法。一般都是已Begin開頭End結尾構成一對,非同步委託方法,外加兩個回撥函式和AsyncState引數,組成非同步操作 的巨集觀體現。所以要做非同步程式設計,不要忘了委託delegate、Begin,End,AsyncCallBack委託,AsyncState例項(在回撥 函式中通過IAsyncResult.AsyncState來強制轉換),IAsycResult(監控非同步),就足以理解非同步真諦了。

 

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/12639172/viewspace-624340/,如需轉載,請註明出處,否則將追究法律責任。

相關文章