首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

.NET 異步檔案 I/O 實戰:從手寫 APM 到 WebClient 妙用

     編輯:關於.NET

痛點:System.IO.File 只有同步方法

File.ReadAllText、File.WriteAllBytes 等靜態方法雖方便,但全是同步阻塞。UI 執行緒呼叫時會凍結介面,伺服器高併發時會耗盡執行緒池。雖然 FileStream 支援原生非同步 (APM/Task),但缺乏高層級的「一次性讀寫整個檔案」封裝。

方案一:手寫 APM 封裝 —— 完全控制,效能最優

核心思路

  1. 使用 FileStream 開啟非同步旗標 (FileOptions.Asynchronous)。
  2. 實作 CopyStreamToStream 遞迴式 BeginRead/EndRead → BeginWrite/EndWrite 循環。
  3. 上層封裝 ReadAllBytesAsync、WriteAllTextAsync 等 API,內部用 MemoryStream 暫存。
  4. 利用 AsyncOperationManager 將完成回調封送回原始同步上下文 (UI 執行緒等)。

關鍵代碼骨架


// 核心遞迴複製
public static void CopyStreamToStream(Stream source, Stream destination, Action<Exception> completed)
{
    byte[] buffer = new byte[0x1000];
    AsyncCallback readCallback = null;
    readCallback = ar => {
        try {
            int read = source.EndRead(ar);
            if (read > 0) {
                destination.BeginWrite(buffer, 0, read, writeAr => {
                    try { destination.EndWrite(writeAr); source.BeginRead(buffer, 0, buffer.Length, readCallback, null); }
                    catch (Exception ex) { completed(ex); }
                }, null);
            } else completed(null);
        } catch (Exception ex) { completed(ex); }
    };
    source.BeginRead(buffer, 0, buffer.Length, readCallback, null);
}

// 公開 API 範例
public static void ReadAllBytesAsync(string path, Action<byte[]> success, Action<Exception> failure)
{
    var asyncOp = AsyncOperationManager.CreateOperation(null);
    var input = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 0x1000, true);
    var output = new MemoryStream((int)input.Length);
    CopyStreamToStream(input, output, e => {
        byte[] bytes = e == null ? output.GetBuffer() : null;
        output.Close(); input.Close();
        asyncOp.Post(_ => e != null ? failure(e) : success(bytes), null);
    });
}

優缺點

  • ✅ 真正的 I/O 完成埠 (IOCP) 非同步,無執行緒阻塞,擴展性最強。
  • ✅ 可擴展進度報告、取消、緩衝區池等高階功能。
  • ❌ 代碼量大、易出錯、維護成本高。

方案二:WebClient + file:// 協議 —— 驚人的簡潔

原理揭秘

WebClient 內部使用 WebRequest 工廠模式。WebRequest.Create("file://C:/test.txt") 會返回 FileWebRequest,它實作了檔案的非同步讀寫。WebClient 封裝了 DownloadDataAsync/UploadDataAsync 等事件式非同步模式 (EAP),並自動處理同步上下文封送。

極簡實作


public static void ReadAllBytesAsync(string path, Action<byte[]> success, Action<Exception> failure)
{
    var wc = new WebClient();
    wc.DownloadDataCompleted += (s, e) => e.Error != null ? failure(e.Error) : success(e.Result);
    wc.DownloadDataAsync(new Uri("file://" + path));
}

public static void WriteAllTextAsync(string path, string contents, Action success, Action<Exception> failure)
{
    var wc = new WebClient();
    wc.UploadStringCompleted += (s, e) => e.Error != null ? failure(e.Error) : success();
    wc.UploadStringAsync(new Uri("file://" + path), contents);
}

額外福利:內建進度報告


wc.DownloadProgressChanged += (s, e) => progressBar.Value = e.ProgressPercentage;
wc.DownloadDataAsync(new Uri(@"file://c:\largeFile.dat"));

DownloadProgressChangedEventArgs 提供 BytesReceived、TotalBytesToReceive、ProgressPercentage,完美整合 WinForms/WPF 進度條。

致命缺陷:執行緒池阻塞

警告:FileWebRequest 實作有缺陷,非同步 I/O 完成時會阻塞執行緒池執行緒等待。大量並發檔案操作會耗盡執行緒池,導致嚴重效能退化甚至死鎖。

適用場景**:偶爾的單檔案操作、UI 應用載入配置、低併發場景。

禁用場景**:高併發伺服器、批量處理大量檔案、長時間運行的服務。

方案三:.NET 4.5+ Task 原生非同步 (現代首選)


// .NET 4.5+ 標準做法
public static async Task<byte[]> ReadAllBytesAsync(string path)
{
    using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true))
    {
        var bytes = new byte[fs.Length];
        await fs.ReadAsync(bytes, 0, bytes.Length);
        return bytes;
    }
}

public static async Task WriteAllTextAsync(string path, string contents)
{
    using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, 4096, true))
    {
        var bytes = Encoding.UTF8.GetBytes(contents);
        await fs.WriteAsync(bytes, 0, bytes.Length);
    }
}
  • ✅ 語法清晰、異常處理自然、編譯器生成狀態機、效能與手寫 APM 相當。
  • ✅ 支援 CancellationToken、IProgress<T>、ValueTask 等現代模式。

綜合對比與選型建議

方案非同步模型真非同步 I/O進度報告適用 .NET 版本推薦指數
手寫 APMAPM (Begin/End)✅ 完美需自行實作2.0+⭐⭐ (教學/極致控制)
WebClient + file://EAP (事件)⚠️ 有缺陷 (阻塞執行緒池)✅ 內建2.0+⭐⭐ (簡單場景/快速原型)
FileStream + TaskTAP (async/await)✅ 完美✅ IProgress4.5+⭐⭐⭐⭐⭐ (現代標準首選)
第三方庫TAP✅視庫而定Standard 2.0+⭐⭐⭐⭐ (功能豐富)

最佳實踐清單

  1. 優先使用 async/await + FileStream,目標 .NET 4.5+ / .NET Core / .NET 5+。
  2. 始終指定 FileOptions.Asynchronous (或 useAsync: true),否則 ReadAsync 會同步完成。
  3. 緩衝區大小:4096 或 8192 位元組通常最佳,避免過大佔用 LOH。
  4. 大檔案分塊:超大檔案考慮 FileStream.Position 分段讀寫,配合 SemaphoreSlim 限制併發度。
  5. 取消支援:傳入 CancellationToken,響應用戶取消或超時。
  6. 避免 WebClient 高併發檔案操作,除非確認 .NET 版本已修復底層缺陷 (Core 3.0+ 部分改善,但仍不推薦)。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved