許多開發者嘗試在不使用 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# 編譯器要求區域變數在使用前已明確指派。解法:先宣告 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);
}
BeginRead → 回調 EndRead → BeginWrite → 回調 EndWrite → BeginRead... 形成循環。try/catch 確保例外透過 done 傳遞給完成委派。AsyncOperationManager.CreateOperation(null) 捕獲當前 SynchronizationContext,Post 確保完成委派在原始內容 (如 UI 執行緒) 執行,支援 WinForms/WPF 直接更新介面。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
BeginRead/Write 內部使用執行緒池包裝同步呼叫,**效能不如同步版**。async { ... }。手寫非同步模式雖繁瑣,但能深入理解 I/O 完成埠、同步內容傳遞與例外流向。實務上優先使用
async/await或Task.Run,僅在底層庫開發或舊版框架維護時採用本文模式。