C# 非同步運算:與 Tim Corey 深入探索異步 Zip
[[academy-video-youtube({"vid": "YgQ3ta6455A", "start_time": "0", "title": "Async Zip in .NET 10 - One Line Create or Extract And More", "creator": "Tim Corey", "length": "24m 49s"})]]
文件壓縮與解壓縮是許多 C# 開發者必要的任務,無論您是在開發桌面應用程式、網頁伺服器,或者只是需要備份資料。 多年來,.NET 開發者可以同步地建立、更新和解壓縮 zip 文件,這可能會阻塞 UI 執行緒或延遲其他操作。 正如 Tim Corey 在他的影片"Async Zip in .NET 10 – One Line Create or Extract And More"中解釋的那樣,.NET 10 引入了 zip 文件的非同步操作,使開發者能夠編寫更高效的程式碼,以執行 I/O 任務而不凍結主應用程式。
C# 中的非同步編程主要基於 Task 和 Task 物件,Task 類作為持續工作的核心組件,讓並行執行成為可能。 Task 非同步編程(TAP)模型為典型的非同步編碼提供了一層抽象,使得非同步操作和異常處理變得更容易。 System.Threading.Tasks.Task 類和相關型別用於在 C# 中實現基於任務的非同步編程模型。
在本文中,我們將跟隨 Tim 的解釋,展示如何在 C# 中執行非同步操作來建立、解壓和選擇性壓縮文件,全部在使用 async/await 模式、Task 物件和正確的異常處理的情況下進行。
非同步編程及 .NET 10 中的 Async Zip 入門
Tim 開始強調 C# 建立和解壓縮 zip 文件的長久能力。 然而,隨著 .NET 10 的出現,開發者現在可以使用非同步程式碼來處理這些任務,而不會阻塞調用的執行緒。 使用 async 方法和 await 關鍵字,建立或解壓大型 zip 文件等操作可以在背景執行緒上運行,允許主 UI 執行緒保持響應。
async 關鍵字只是一個裝飾器,告知 C# 編譯器該方法至少包含一次 await 關鍵字的使用。 await 關鍵字只能用在 async 方法中,並且後面必須跟著一個 Task 或 Task<TResult> 物件。
正如 Tim 解釋的,這在執行多個任務(例如網路調用、資料庫查詢或文件 I/O)的應用程式中特別重要,因為等待任務讓 CPU 工作不會阻塞調用執行緒。 用 .Result 或 .Wait() 阻塞非同步程式碼會將其變回同步程式碼,這可能導致死鎖。
Async 方法返回 Task 或 Task<TResult> 物件,而返回任務使得正確的任務組合和並發成為可能。 沒有 await 的方法是同步執行的。 通過從非同步方法返回 Task 物件,您可以協調多個持續進行的操作並更有效地處理異常。
專案設置
Tim 開始打開 Visual Studio 2026 並建立了一個名為 ZipFilesApp 的控制台應用程式。他提到即使是靜態 async Task Main 方法也可以用來在程式的進入點執行非同步任務,避免阻塞主執行緒。 在改編現有程式碼時,您常常可以將同步程式碼包裝在 Tasks 中或實現 async 變體以在不必重寫整個程式碼庫的情況下引入非同步行為。
然後他新增了必需的 using 指令:
using System.IO.Compression;
這使得可以使用非同步方法,如 CreateFromDirectoryAsync 和 ExtractToDirectoryAsync。 在建立調用方法時,使用一致的命名約定很重要,如對非同步方法追加 'Async' 後綴,以明確區分它們與同步方法。 不一致的方法命名可能導致關於方法是同步還是非同步的混淆。
接下來,Tim 准備了三個字串變數來定義 zip 操作所需的路徑:
源目錄——要壓縮的文件夾。
目標 zip 文件——保存 zip 文件的完整路徑。
- 目標目錄——提取文件將被儲存的文件夾。
Tim 提到在字串字面值中使用 @ 符號以避免反斜杠轉義,這是編寫 Windows 路徑時在同步程式碼中常見的問題。 本文中提供了程式碼樣本和程式碼片段,以展示如何在 C# 中實現非同步編程。
使用非同步方法建立 Zip 文件
在 3:47,Tim 演示了一個使用靜態 async Task 方法的單行非同步操作:
await ZipFile.CreateFromDirectoryAsync(
sourceDirectory,
destinationZipFile,
CompressionLevel.SmallestSize,
includeBaseDirectory: false
);
在這裡,Tim 解釋了幾個重要方面:
async 關鍵字標記這個非同步方法,允許使用 await。
await 暫停此 async 方法的執行,直到任務完成,允許其他任務或主 UI 執行緒繼續運行。 這說明了 await 如何工作並暫停執行以確保非同步操作(如文件 I/O)中的非阻塞行為。
CompressionLevel.SmallestSize 確保高效壓縮。
- includeBaseDirectory 參數控制根文件夾是否被包含在 zip 中。
這個例子顯示了 C# 中非同步工作流如何由 async 和 await 關鍵字管理,使得程式碼更易於維護和簡化異常處理。 await 關鍵字只能用在 async 方法中,例如公共 async Task 方法。 當遇到 await 時,C# 編譯器生成一個狀態機來管理執行流,暫停執行、啟動非同步任務,然後在任務完成後恢復執行。 如果 async 方法返回一個值,可以將結果捕獲在變數中,如 int result,以便後續使用。 通過使用 async/await,開發者不必手動建立背景執行緒或執行緒池任務,因為編譯器會自動生成必要的狀態機和支持程式碼。
使用 Await Task 非同步提取 Zip 文件
Tim 繼續通過使用另一個 async 方法來提取 zip 文件的非同步操作:
await ZipFile.ExtractToDirectoryAsync(
destinationZipFile,
destinationDirectory,
overwriteFiles: false
);
他指出,overwriteFiles 參數在文件夾已經存在的情況下處理異常處理。 預設情況下,如果文件已經存在,提取將失敗,但將其設置為 true 允許非同步操作替換現有文件。
在處理非同步程式碼時,強大的錯誤處理至關重要。 Tim 強調使用 try-catch 塊來處理 async 方法中的異常。 未能在非同步程式碼中正確處理異常可能導致靜默失敗或意外崩潰,因為非同步任務中拋出的異常可能不會立即可見。 總是處理異常以確保應用程式行為的可靠性。
Tim 注意到,這段非同步程式碼對多個任務來說是安全的,意味著它不會阻塞正在同時發生的其他請求或網路調用。 使用 await 關鍵字,調用的執行緒在任務完成之前被釋放,創造出一個響應的程式流程。
此外,當定義 async 方法時,async void 只應用於事件處理程式,例如 GUI 應用程式中的按鈕點擊。 在這些情況下,方法簽名通常包含 (object sender, EventArgs e),其中 object sender 標識事件的源。 在事件處理程式之外使用 async void 可能會導致不可預測的行為和很難診斷的錯誤,因此對於大多數非同步方法請優先使用 async Task。
選擇性地新增文件到 Zip
有時候,開發者需要比簡單地壓縮整個文件夾更高的控制。 Tim 演示通過建立 FileStream 來選擇性地執行非同步操作:
await using FileStream zipStream = new FileStream(
destinationZipFile,
FileMode.Create,
FileAccess.Write,
FileShare.None,
bufferSize: 4096,
useAsync: true
);
在非同步新增文件時,每個文件新增都代表一個特定的任務。 通過同時啟動所有文件新增的任務,然後使用像 Task.WhenAll 方法等待它們的完成,您可以顯著提高性能。 用這種方式管理運行的任務對於高並發場景至關重要,例如當伺服器需要一次處理多個請求時。 這種方法也常見於與遠程伺服器的交互中,允許應用程式保持響應,同時等候資料。
Tim 解釋了每一個細節:
FileMode.Create – 確保建立一個新的 zip 文件或覆蓋原文件。
FileAccess.Write – 允許寫入文件。
FileShare.None – 防止在寫入時其他任務存取該文件。
- useAsync: true – 啓用異步文件寫入,核心異步操作的一部分。
他指出,這個上下文中的非同步編程避免了在任務完成之前阻塞主執行緒,這在處理多個任務的網頁伺服器上特別有用。 高併發伺服器利用非同步編程來有效處理多個同時進行的客戶端請求,而不必為每個連接生成新執行緒。 然而,順序地編寫非同步程式碼可能使其更難以閱讀和除錯,因為非同步任務可能以無序方式完成。
建立 Zip 存檔並新增文件
接下來,Tim 非同步地建立了一個 ZipArchive:
using ZipArchive archive = await ZipArchive.CreateAsync(
zipStream,
ZipArchiveMode.Create,
leaveOpen: false,
entryNameEncoding: null
);
await 表達式確保執行暫停直到任務完成,使得繼續新增文件的操作安全。 Async 和 await 簡化了非同步操作的管理,讓程式碼與傳統的回調方法相比更易於閱讀和維護。
重要的是要注意相同的程式碼可以在不同的同步上下文中表現不同。 例如,在 WPF 或 WinForms 應用程式中,存在同步上下文可能會導致 await 呼叫後的延續運行在主 UI 執行緒上,而在控制台應用程式中,則不存在這樣的上下文。 這可能影響您的非同步程式碼的執行方式。
在編寫庫程式碼時,考慮在 await 呼叫後使用 ConfigureAwait(false) 以避免不必要的上下文切換並提高性能。
- 使用 Path.GetRelativePath,Tim 解釋如何在 zip 中保持文件夾結構,這在執行具有多個文件的非同步工作流時至關重要:
foreach (string filePath in Directory.GetFiles(sourceDirectory, "*", SearchOption.AllDirectories))
{
string relativePath = Path.GetRelativePath(sourceDirectory, filePath);
await archive.CreateEntryFromFileAsync(filePath, relativePath).ConfigureAwait(false);
}
這展示了非同步程式碼的實踐,每個文件新增都是一次非同步調用,並且 await 關鍵字確保了正確的順序,同時仍在執行非阻塞的 I/O。 非同步程式碼允許當前執行緒保持不被阻塞,提高響應性,並允許執行緒在等待 I/O 操作完成時執行其他工作。
處理文件存取與異常處理
Tim 強調了一個常見的異常處理場景:在原始 FileStream 任務完成之前嘗試提取 zip 文件。 使用文件範圍的 async void 方法或 async Task 方法而沒有適當的範圍使用語句可能會導致:
無法訪問該文件,因為它正在被另一個進程使用。解決方案是將非同步操作包裹在傳統的範圍使用塊中以在提取之前釋放資源:
await using (FileStream zipStream = new FileStream(...))
{
using ZipArchive archive = await ZipArchive.CreateAsync(zipStream, ...);
// 非同步添加文件
}
// 現在可以安全地提取這確保了任務物件完成並釋放同步上下文,避免文件鎖定錯誤。
結論:高效的 .NET 10 非同步 Zip
Tim 總結道,.NET 10 中的非同步 zip 操作允許開發者高效地執行非同步操作,避免阻塞主執行緒,並與其他任務(如網路調用或資料庫查詢)無縫整合。
使用靜態 async Task 方法、async 修飾符和 await 表達式,開發者可以:
壓縮或解壓縮 zip 文件而不凍結 UI 執行緒。
安全地處理多個執行緒而不需要手動管理執行緒池。
在保存文件夾結構的同時執行選擇性壓縮。
- 確保正確的異常處理和資源清理。
Tim 的例子表明,文件操作中的 async/await 不僅便捷,還是現代 C# 非同步編程的必要部分。
通過觀看他的影片,開發者可以利用 async 方法、await 調用和非同步工作流來編寫高效程式碼,無縫處理簡單和複雜的文件壓縮任務。

