程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> 什麼是.Net的異步機制(異步Stream讀寫) - step 4

什麼是.Net的異步機制(異步Stream讀寫) - step 4

編輯:關於.NET

異步的Stream讀/寫操作

下面是繼承於System.IO.Stream的類

System.IO.Stream
  Microsoft.JScript.COMCharStream
  System.IO.BufferedStream
  System.IO.FileStream
  System.IO.MemoryStream
  System.IO.UnmanagedMemoryStream
  System.Security.Cryptography.CryptoStream
  System.Printing.PrintQueueStream
  System.IO.Pipes.PipeStream
  System.Data.OracleClient.OracleBFile
  System.Data.OracleClient.OracleLob
  System.IO.Compression.DeflateStream
  System.IO.Compression.GZipStream
  System.Net.Sockets.NetworkStream
  System.Net.Security.AuthenticatedStream

在System.IO.Stream中提供了異步的讀/寫(Read/Write)行為,上面繼承於System.IO.Stream的類都具有同樣的異步操作行為.在.Net Framework框架中,微軟設計師使用Begin+同步方法名 / End+同步方法名來設計異步方法的規則,基本上我們在微軟MSDN看到的 BeginXXX + EndXXX都是異步的方法,並且當我們在某個類中看到BeginInvoke / EndInvoke,都是微軟提供的最原始的異步方法.在System.IO.Stream類中表現為BeginRead+EndRead / BeginWrite/EndWrite.

我們來看一個例子,FileStream(System.IO),Read / BeginRead+EndRead,讀取文件內容,開始我們使用同步方法.

同步調用

Code1.1

1static class Program
2  {
3    static string path = @"c:\file.txt";//確保你本地有這個文件
4    const int bufferSize = 5;//演示,一次只讀取5 byte
5    static void Main()
6    {
7      FileStream fs = new FileStream(path, FileMode.Open,
8FileAccess.Read, FileShare.Read, 20480, false);//同步調用false
9      using (fs)//使用using來釋放FileStream資源
10      {
11        byte[] data = new byte[bufferSize];
12        StringBuilder sb = new StringBuilder(500);
13        int byteReads;
14        do// 不斷循環,直到讀取完畢
15        {
16          byteReads = fs.Read(data, 0, data.Length);
17          sb.Append(Encoding.ASCII.GetString(data, 0, byteReads));
18        } while (byteReads > 0);
19        Console.WriteLine(sb.ToString());//輸出到工作台
20
21      }//自動清除對象資源,隱式調用fs.Close();
22      Console.ReadLine();// 讓黑屏等待,不會直接關閉..
23    }
24  }

方法非常簡單,它會構造一個 FileStream 對象,調用 Read方法,不斷循環讀取數據。C# using 語句可確保完成數據處理後會關閉該 FileStream 對象。

下面我們看異步調用(BeginRead/EndRead)

異步調用

Code1.2

1static class Program
2  {
3    static string path = @"c:\file.txt";//確保你本地有這個文件
4    const int bufferSize = 5;//演示,一次只讀取5 byte
5    static byte[] data;
6    static void Main()
7    {
8      data = new byte[bufferSize];
9      FileStream fs = new FileStream(path, FileMode.Open,
10FileAccess.Read, FileShare.Read, 20480, true);//設置異步調用true, 注意0
11
12      //異步讀取文件,把FileStream對象作為異步的參數// <-
13      AsyncCallback callback = new AsyncCallback(OnReadCompletion);
14      IAsyncResult async = fs.BeginRead(data, 0, bufferSize, callback, fs); // <-
15
16      Console.ReadLine();// 讓黑屏等待,不會直接關閉..
17    }
18    static void OnReadCompletion(IAsyncResult asyncResult)
19    {
20      FileStream fs = asyncResult.AsyncState as FileStream;
21      int bytesRead = fs.EndRead(asyncResult);
22      //輸出到工作台
23      Console.Write(Encoding.ASCII.GetString(data, 0, bytesRead));
24      //不斷循環,直到讀取完畢
25      if (bytesRead > 0)
26        fs.BeginRead(data, 0, bufferSize, OnReadCompletion, fs);
27      else
28        fs.Close(); //當全部讀取完畢,顯式釋放資源
29    }
30  }

方法是使用BeginRead和EndRead 完成的, 我們注意到方法不能使用 C# using 語句(釋放資源),因為 FileStream 是在一個主線程中打開,然後在另一個線程中關閉的,而是通過把FileStream 作為參數的形式來在另外一個線程中關閉的(fs.Close();),查看紅色部分.

注意0:創建FileStram 對象,如果沒有FileStream fs = new FileStream(path, FileMode.Open,FileAccess.Read, FileShare.Read, 20480, true); bool useAsync=true 或者構造FileStream(String, FileMode, FileAccess, FileShare, Int32, FileOptions) FileOptions = FileOptions.Asynchronous 時, 即使使用了BeginRead/EndRead, 程序也是在同步執行方法,FileStream 對象會使用其他線程來模仿異步行為,反而降低了應用程序的性能.

下面我將通過使用C# 匿名方法(C# 2.0) 和 lambda 表達式(C# 3.0引入的一個新功能) 來完成上面操作,如果對這個不熟悉的朋友可以查看下面文章.

匿名方法:http://www.microsoft.com/china/msdn/library/langtool/vcsharp/CreElegCodAnymMeth.mspx?mfr=true

lambda 表達式:http://msdn.microsoft.com/zh-cn/magazine/cc163362.aspx

C# 匿名方法 和 lambda 表達式

1,匿名方法:

Code1.3

1static class Program
2  {
3    static string path = @"c:\file.txt";//確保你本地有這個文件
4    const int bufferSize = 5;//演示,一次只讀取5 byte
5    static void Main()

6    {
7      byte[] data = new byte[bufferSize];
8      //[1]
9      FileStream fs = new FileStream(path, FileMode.Open,
10FileAccess.Read, FileShare.Read, 20480, true);//設置異步調用true
11      //使用匿名委托方式
12      AsyncCallback callback = null; //注意1
13      callback = delegate(IAsyncResult asyncResult)//匿名方法
14      {
15        int bytesRead = fs.EndRead(asyncResult);//[2]
16        Console.Write(Encoding.ASCII.GetString(data, 0, bytesRead));//輸出到工作台
17        //不斷循環,直到讀取完畢
18        if (bytesRead > 0)
19          fs.BeginRead(data, 0, bufferSize, callback, null);//[3]
20        else
21          fs.Close();//[4]
22      };
23
24      //異步讀取文件
25      IAsyncResult async = fs.BeginRead(data, 0, bufferSize, callback, null);
26
27      Console.ReadLine();// 讓黑屏等待,不會直接關閉..
28    }
29  }

對比Code1.2代碼我們可以看出, 匿名方法非常出色的完成我們功能, 在匿名方面體內 fs ([2][3][4])像普通變量一樣執行引用FileStream([1]) 對象,而不需要任何的類型轉換. 對象在方法之間輕松實現傳遞,並且從一個線程輕松遷移到另一個線程, 對APM 編程而言這是十分完美的,但實際上編譯器會重新編寫您的代碼,從堆棧中取出這些變量,並將它們作為字段嵌入對象。由於編譯器會自動執行所有的工作,您可以很輕松地將最後一個參數的空值傳遞到 BeginRead 方法,因為現在沒有必要再在方法和線程之間顯式傳遞的數據了。

注意1: 必須先AsyncCallback callback = null; 要不編程器會告訴你錯誤:” Use of unassigned local variable 'callback '”.

2,Lambda 表達式

我們只需要簡單的修改(在執行同一操作,lambda 表達式語法比 C# 匿名方法更簡潔),匿名方法,把Code1.3中紅色部分的

callback = delegate(IAsyncResult asyncResult)

修改成

callback = asyncResult =>

下面是完整代碼.

Code1.4

static class Program
  {
    static string path = @"c:file.txt";//確保你本地有這個文件
    const int bufferSize = 5;//演示,一次只讀取5 byte
    static void Main()
    {
      byte[] data = new byte[bufferSize];
      FileStream fs = new FileStream(path, FileMode.Open,
FileAccess.Read, FileShare.Read, 20480, true);//設置異步調用true
      //使用lambda 表達式
      AsyncCallback callback = null;//注意1
      callback = asyncResult =>
      {
        int bytesRead = fs.EndRead(asyncResult);
        Console.Write(Encoding.ASCII.GetString(data, 0, bytesRead));//輸出到工作台
        //不斷循環,直到讀取完畢
        if (bytesRead > 0)
          fs.BeginRead(data, 0, bufferSize, callback, null);
        else
          fs.Close();
      };
      //異步讀取文件
      IAsyncResult async = fs.BeginRead(data, 0, bufferSize, callback, null);
      Console.ReadLine();// 讓黑屏等待,不會直接關閉..
    }
  }

最後,我們來看看異步的寫操作(BeginWrite/EndWrite)

Code2

1 static class Program
2 {
3 static void Main()
4 {
5 FileStream fs = new FileStream("text.txt", FileMode.Create,
6FileAccess.ReadWrite, FileShare.None, 20480, true);//設置異步調用true
7 //輸入信息
8 Console.Write("Please Enter:");
9 byte[] data = Encoding.ASCII.GetBytes(Console.ReadLine());
10
11 //異步寫文件
12 IAsyncResult async = fs.BeginWrite(data, 0, data.Length, asyncResult =>
13 {
14 fs.EndWrite(asyncResult);//寫文件介紹,輸出到text.txt文件中.
15 fs.Close();
16
17 }, null);
18
19 Console.ReadLine();// 讓黑屏等待,不會直接關閉..
20 }
21 }

大家覺得是否很簡單呢? 基本上所有具有異步行為的流(繼承於System.IO.Stream)操作都可以按照類似於上面的代碼編寫. 當然其他異步行為也可以使用上面代碼中的技巧. 在System.IO.Stream 中,提供了ReadTimeout/WriteTimeout 的超時處理,但是基類中是不支持的.會報 InvalidOperationException 異常,反編譯可以看到throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_TimeoutsNotSupported")).

下篇文章我會提供其他的例子來說明異步中的線程間通信.采用Window Forms程序.

本文配套源碼

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved