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

.NET 進階議題:非同步串流複製的正確實作模式

     編輯:關於.NET

許多開發者嘗試在不使用 C# 迭代器 (yield return) 的情況下,手寫真正的非同步串流複製 (避免佔用執行緒池執行緒)。本文以 CopyStreamToStream 為例,示範 C# 匿名方法與 Visual Basic 手工閉包 兩種語言的完整實作,並解析 AsyncOperation 跨執行緒同步內容傳遞機制。

同步版本基準


public static void CopyStreamToStream(Stream source, Stream destination, Action completed) {
    byte[] buffer = new byte[0x1000];
    int read;
    try {
        while ((read = source.Read(buffer, 0, buffer.Length)) > 0) {
            destination.Write(buffer, 0, read);
        }
        completed?.Invoke(source, destination, null);
    } catch (Exception exc) {
        completed?.Invoke(source, destination, exc);
    }
}

同步版會阻塞呼叫執行緒直到完成。

偽非同步陷阱

  • ThreadPool.QueueUserWorkItem 或 BeginInvoke 只是將**同步邏輯**移至執行緒池,**Read/Write 本身仍為同步阻塞**,併發 100 次複製需 100 條執行緒,受限於執行緒池擴容速率,總耗時反而增加。
  • 真正的非同步需使用 BeginRead/EndRead、BeginWrite/EndWrite,**不佔用執行緒等待 I/O 完成**。

C# 實作:連續傳遞風格 (CPS) + 匿名方法遞迴

核心難點:遞迴委派的未指派變數

C# 編譯器要求區域變數在使用前已明確指派。解法:先宣告 AsyncCallback rc = null; 再指派 Lambda。

完整實作


public static void CopyStreamToStream(Stream source, Stream destination, Action completed) {
    byte[] buffer = new byte[0x1000];
    // 捕獲 SynchronizationContext (如 WinForms UI 執行緒)
    AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(null);
    Action done = ex => {
        if (completed != null)
            asyncOp.Post(_ => completed(source, destination, ex), null);
    };

    AsyncCallback rc = null;
    rc = readResult => {
        try {
            int read = source.EndRead(readResult);
            if (read > 0) {
                destination.BeginWrite(buffer, 0, read, writeResult => {
                    try {
                        destination.EndWrite(writeResult);
                        // 寫入完成,發起下一次讀取
                        source.BeginRead(buffer, 0, buffer.Length, rc, null);
                    } catch (Exception exc) { done(exc); }
                }, null);
            } else {
                done(null); // 讀取結束
            }
        } catch (Exception exc) { done(exc); }
    };

    // 啟動第一次讀取
    source.BeginRead(buffer, 0, buffer.Length, rc, null);
}

關鍵設計點

  1. 非同步鏈:BeginRead → 回調 EndRead → BeginWrite → 回調 EndWrite → BeginRead... 形成循環。
  2. 例外處理:每個回調內部 try/catch 確保例外透過 done 傳遞給完成委派。
  3. AsyncOperation:AsyncOperationManager.CreateOperation(null) 捕獲當前 SynchronizationContext,Post 確保完成委派在原始內容 (如 UI 執行緒) 執行,支援 WinForms/WPF 直接更新介面。

Visual Basic 實作:手工閉包類別

VB 9 (VS 2008) 不支援多行 Lambda,需手工建立狀態類別 CopyData 傳遞上下文。


Public Shared Sub CopyStreamToStream(source As Stream, destination As Stream, completed As Action(Of Stream, Stream, Exception))
    Dim buffer(&H1000) As Byte
    Dim data = New CopyData With {
        .Source = source, .Destination = destination,
        .Buffer = buffer, .Completed = completed,
        .AsyncOperation = AsyncOperationManager.CreateOperation(Nothing)
    }
    source.BeginRead(buffer, 0, buffer.Length, AddressOf ReadCallbackSub, data)
End Sub

Private Shared Sub ReadCallbackSub(readResult As IAsyncResult)
    Dim copyData = CType(readResult.AsyncState, CopyData)
    Try
        Dim read = copyData.Source.EndRead(readResult)
        If read > 0 Then
            copyData.Destination.BeginWrite(copyData.Buffer, 0, read, AddressOf WriteCallbackSub, copyData)
        Else
            copyData.AsyncOperation.Post(Function() Done(copyData, Nothing), Nothing)
        End If
    Catch ex As Exception
        copyData.AsyncOperation.Post(Function() Done(copyData, ex), Nothing)
    End Try
End Sub

Private Shared Sub WriteCallbackSub(writeResult As IAsyncResult)
    Dim copyData = CType(writeResult.AsyncState, CopyData)
    Try
        copyData.Destination.EndWrite(writeResult)
        copyData.Source.BeginRead(copyData.Buffer, 0, copyData.Buffer.Length, AddressOf ReadCallbackSub, copyData)
    Catch ex As Exception
        copyData.AsyncOperation.Post(Function() Done(copyData, ex), Nothing)
    End Try
End Sub

Private Shared Function Done(copyData As CopyData, ex As Exception) As Boolean
    If copyData.Completed IsNot Nothing Then copyData.Completed(copyData.Source, copyData.Destination, ex)
    Return True
End Function

Private Class CopyData
    Public Source, Destination As Stream
    Public Buffer() As Byte
    Public AsyncOperation As AsyncOperation
    Public Completed As Action(Of Stream, Stream, Exception)
End Class

效能考量與適用場景

  • 基礎 Stream 類別:BeginRead/Write 內部使用執行緒池包裝同步呼叫,**效能不如同步版**。
  • FileStream (useAsync=true):呼叫 Windows 重疊 I/O (IOCP),**真正非同步**,不佔用執行緒,適合高擴展性伺服器應用。
  • NetworkStream、SslStream、GZipStream:多數支援真正非同步。

生態系演進

  • F# 非同步工作流:語言層面原生支援 async { ... }。
  • CCR (Concurrency and Coordination Runtime):迭代器基礎的非同步協調庫。
  • Jeffrey Richter AsyncEnumerator:更通用的迭代器模式實作。
  • C# 5.0 async/await (後續版本):編譯器自動生成狀態機,徹底解決手寫 CPS 複雜度。

手寫非同步模式雖繁瑣,但能深入理解 I/O 完成埠、同步內容傳遞與例外流向。實務上優先使用 async/await 或 Task.Run,僅在底層庫開發或舊版框架維護時採用本文模式。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved