所有繼承 System.IO.Stream 的類別 (FileStream、NetworkStream、CryptoStream 等) 均支援標準 APM (Asynchronous Programming Model) 非同步讀寫:BeginRead/EndRead、BeginWrite/EndWrite。本文從同步基準開始,逐步演示 APM、匿名方法、Lambda 三種寫法,並說明關鍵建構子參數 useAsync=true 的效能影響。
using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 20480, false))
{
byte[] buffer = new byte[5];
int read;
var sb = new StringBuilder();
while ((read = fs.Read(buffer, 0, buffer.Length)) > 0)
sb.Append(Encoding.ASCII.GetString(buffer, 0, read));
Console.WriteLine(sb);
}
關鍵:建構子最後一個參數 useAsync=true (或 FileOptions.Asynchronous),否則內部仍同步模擬,反而降低效能。
FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 20480, true);
byte[] buffer = new byte[5];
fs.BeginRead(buffer, 0, buffer.Length, OnReadComplete, fs);
void OnReadComplete(IAsyncResult ar)
{
FileStream fs = (FileStream)ar.AsyncState;
int read = fs.EndRead(ar);
Console.Write(Encoding.ASCII.GetString(buffer, 0, read));
if (read > 0)
fs.BeginRead(buffer, 0, buffer.Length, OnReadComplete, fs);
else
fs.Close(); // 手動釋放,不可 using
}
編譯器自動將區域變數捕獲為閉包類別欄位,免去手動在 AsyncState 傳遞 FileStream。
FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 20480, true);
byte[] buffer = new byte[5];
AsyncCallback callback = null;
callback = delegate(IAsyncResult ar)
{
int read = fs.EndRead(ar);
Console.Write(Encoding.ASCII.GetString(buffer, 0, read));
if (read > 0) fs.BeginRead(buffer, 0, buffer.Length, callback, null);
else fs.Close();
};
fs.BeginRead(buffer, 0, buffer.Length, callback, null);
注意:必須先宣告 AsyncCallback callback = null;,否則編譯器報「使用未指派區域變數」。
FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 20480, true);
byte[] buffer = new byte[5];
AsyncCallback callback = null;
callback = ar =>
{
int read = fs.EndRead(ar);
Console.Write(Encoding.ASCII.GetString(buffer, 0, read));
if (read > 0) fs.BeginRead(buffer, 0, buffer.Length, callback, null);
else fs.Close();
};
fs.BeginRead(buffer, 0, buffer.Length, callback, null);
FileStream fs = new FileStream("out.txt", FileMode.Create, FileAccess.ReadWrite, FileShare.None, 20480, true);
byte[] data = Encoding.ASCII.GetBytes(Console.ReadLine());
fs.BeginWrite(data, 0, data.Length, ar => { fs.EndWrite(ar); fs.Close(); }, null);
Stream.ReadTimeout/WriteTimeout 在基類預設不支援,直接拋出 InvalidOperationException,需自行實作計時器或使用 CancellationToken (現代模式)。
下一篇將深入 IAsyncResult 核心介面與 AsyncWaitHandle 同步原語應用。