異步的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程序.
本文配套源碼