.NET 10 中的異步壓縮:單行創建或提取
在C#中一直可以透過System.IO.Compression進行Zip檔案操作,但每個呼叫都是同步的,這意味著在歸檔完成寫入或讀取之前,線程會被阻塞。 .NET 10 的一組非同步過載改變了這一點,讓您可以在不綁定調用線程的情況下創建、提取和填充zip檔。
本操作指南基於Tim Corey最近的指南,展示了.NET 10中的新非同步zip過載。我們將探討三個逐步詳細的方法:用一行代碼建立一個檔案、一次呼叫來解壓縮它,以及一個手動方法來獲得完整的控制,同時檢查一個範圍的using陷阱。
設定:路徑和來源資料夾
[0:28 - 1:55] 設定從一個針對.NET 10的控制台應用程式以及單一使用指令開始:
using System.IO.Compression;
using System.IO.Compression;
三個字串變數定義了整個演示中使用的路徑:
string sourceDirectory = @"C:\temp\test";
string destinationZipFile = @"C:\temp\archive.zip";
string destinationDirectory = @"C:\temp\extracted";
string sourceDirectory = @"C:\temp\test";
string destinationZipFile = @"C:\temp\archive.zip";
string destinationDirectory = @"C:\temp\extracted";
逐字字串前綴 (@) 避免需要重複反斜杠。 sourceDirectory 是要壓縮的資料夾。destinationZipFile 是將要創建的歸檔的完整路徑。 destinationDirectory 是提取時內容將放置的地方。
一個實用的警告:絕不要將sourceDirectory內部的一個路徑。 將zip寫入被壓縮的資料夾中會導致一個遞歸讀取循環,從而中斷流程。
測試資料夾在根目錄下包含兩個檔案,以及一個子資料夾中的第三個檔案,在探索includeBaseDirectory選項和手動方法中的相對路徑處理時至關重要。
用一行程式建立一個歸檔
[2:35 - 4:20] 非同步建立呼叫是同步ZipFile.CreateFromDirectory的直接替代:
await ZipFile.CreateFromDirectoryAsync(
sourceDirectory,
destinationZipFile,
CompressionLevel.SmallestSize,
includeBaseDirectory: false);
await ZipFile.CreateFromDirectoryAsync(
sourceDirectory,
destinationZipFile,
CompressionLevel.SmallestSize,
includeBaseDirectory: false);
CompressionLevel.Fastest則相反。 對於大多數開發場景來說,差別可以忽略不計,但在處理大量歸檔的網頁伺服器上,這種取捨值得評估。
false (默認)下,zip直接打開到文件。 傳遞test資料夾,實際的文件坐落於其中。 大多數使用情境下將此設置為false是有益的。
用一行程式解壓一個歸檔
[4:45 - 5:55] 解壓遵循相同模式:
await ZipFile.ExtractToDirectoryAsync(
destinationZipFile,
destinationDirectory,
overwriteFiles: false);
await ZipFile.ExtractToDirectoryAsync(
destinationZipFile,
destinationDirectory,
overwriteFiles: false);
如果false,若歸檔中的任何檔案已在目標路徑存在,則會引發異常。 設置為true以便靜默替換現有檔案。 兩次運行解壓兩次運行解壓說明了這一點:第一次成功創建資料夾,而第二次則會引發異常,除非指定了overwriteFiles: true。
選擇性的壓縮:逐個添加檔案
[6:15 - 9:50] 使用一行程式壓縮整個目錄而不進行過濾。 當您需要僅包含特定檔案時,您可以使用FileStream 和ZipArchive手動構建歸檔:
await using FileStream zipStream = new FileStream(
destinationZipFile,
FileMode.Create,
FileAccess.Write,
FileShare.None,
bufferSize: 4096,
useAsync: true);
using ZipArchive archive = await ZipArchive.CreateAsync(
zipStream,
ZipArchiveMode.Create,
leaveOpen: false,
entryNameEncoding: null);
await using FileStream zipStream = new FileStream(
destinationZipFile,
FileMode.Create,
FileAccess.Write,
FileShare.None,
bufferSize: 4096,
useAsync: true);
using ZipArchive archive = await ZipArchive.CreateAsync(
zipStream,
ZipArchiveMode.Create,
leaveOpen: false,
entryNameEncoding: null);
這裡有幾個參數值得理解。 FileMode.Create會覆蓋該路徑上的任何現有檔案。 FileMode.CreateNew會在檔案已存在時引發異常。 FileShare.None在寫入歸檔時獨佔鎖定檔案,防止其他過程在操作中讀取或寫入它。
useAsync: true在FileStream上啟用了作業系統級的非同步I/O。 注意,如果沒有實際在下游使用非同步調用就設置這個,可能會顯著降低處理速度,有時甚至會慢上十倍。 因為條目寫入循環使用useAsync: true是在這裡正確的選擇。 在同步代碼路徑上,將其保留為false默認值。
ZipArchive上的leaveOpen: false指示它在釋放時關閉並刷新下層流。 entryNameEncoding: null保持默認編碼,文檔建議除非有特定原因,否則不要更改它。
歸檔準備好了,檢索源內容並寫入每個條目:
string[] files = Directory.GetFiles(sourceDirectory, "*", SearchOption.AllDirectories);
foreach (string filePath in files)
{
string relativePathAndName = Path.GetRelativePath(sourceDirectory, filePath);
await archive.CreateEntryFromFileAsync(filePath, relativePathAndName);
}
string[] files = Directory.GetFiles(sourceDirectory, "*", SearchOption.AllDirectories);
foreach (string filePath in files)
{
string relativePathAndName = Path.GetRelativePath(sourceDirectory, filePath);
await archive.CreateEntryFromFileAsync(filePath, relativePathAndName);
}
使用"</em>.txt"將限制歸檔為文本檔案,例如。
sourceDirectory的部分。 這就是存儲在zip中的條目名稱,忠實地重現子資料夾層次結構。 如果您傳遞Path.GetFileName(filePath),每個條目都會無論原始位置如何落在zip根目錄。 這會生成一個平坦的歸檔,但如果有兩個條目在不同子資料夾中共享一個文件名,則可能會冒著名稱碰撞的風險。
範圍內的Using陷阱
[9:50 - 11:30] 如果您嘗試在手動zip塊之後添加一個解壓呼叫,您將遇到一個文件鎖定異常:The process cannot access the file because it is being used by another process。 這是因為zipStream上的using指令使用了文件範圍語法,這意味著流在文件結尾之前保持打開狀態,而不是在zip塊的大括號處關閉。 解壓嘗試在鎖仍然持有時運行。
轉換為有括號的形式解決了這個問題:
// Before (file-scoped: stream stays open until end of file)
await using FileStream zipStream = new FileStream(...);
// After (block-scoped; stream released at the closing brace)
await using (FileStream zipStream = new FileStream(...))
{
// zip operations here
}
// stream is now closed; extraction can proceed safely
// Before (file-scoped: stream stays open until end of file)
await using FileStream zipStream = new FileStream(...);
// After (block-scoped; stream released at the closing brace)
await using (FileStream zipStream = new FileStream(...))
{
// zip operations here
}
// stream is now closed; extraction can proceed safely
將zip工作包裹在大括號中並去除尾隨的分號將流的生命週期限制到那個明確的範圍。 一旦執行離開關閉的大括號,鎖會被釋放,隨後的提取呼叫可以在不衝突的情況下打開相同的文件。
結論
[11:40 - end] 單行程式處理常見情況:ExtractToDirectoryAsync來解壓它。 當您需要控制哪些檔案進入檔案時,手動ZipArchive方法提供了在條目級別的過濾、重新命名和路徑重寫。
重申一下:添加ZipArchive路徑。 如果流必須在後續代碼運行前釋放,請用大括號來界定您的using塊。 這些新增功能使非同步/等待模式在.NET 10的完整zip工作流程中可用。
