跳至頁尾內容
Iron Academy Logo
學習C#
學習C#

其他類別

在.NET 10中進行非同步壓縮:一行建立或提取

Tim Corey
~12m

在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";

逐字字串前綴(@)避免了需要重複反斜槓。 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] 一行程式碼的方法壓縮整個目錄而無需過濾。 當您需要僅包含特定檔案時,需使用FileStreamZipArchive手動建立檔案:

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

在Zip檔案上,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根目錄,不論其原始位置為何。 這會生成一個平坦的檔案,但如果兩個條目在不同子資料夾中共享同一個檔名,會存在名稱衝突的風險。

範圍使用的陷阱

[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

用括弧包圍壓縮工作並移除末尾的分號將流的生命週期限制到該明確範圍內。 一旦執行離開結束括弧,鎖定會被釋放,隨後的解壓程式呼叫可以在沒有衝突的情況下開啟相同檔案。

結論

[11:40 - 結尾] 單行程式碼處理常見案例:ExtractToDirectoryAsync用於解壓它。 當您需要控制進入檔案的檔案時,手動ZipArchive方法提供條目級過濾、重命名和路徑重寫。

回顧一下:新增ZipArchive路徑。 如果流必須在後續程式碼運行前釋放,則用括弧為您的using區塊設置範圍。 這些新增功能讓async/await模式可以在.NET 10中遍布全流水處理。

在Tim Corey的YouTube頻道觀看完整影片並隨著程式碼實時進行。

Hero Worlddot related to 在.NET 10中進行非同步壓縮:一行建立或提取
Hero Affiliate related to 在.NET 10中進行非同步壓縮:一行建立或提取

分享您所愛以賺取更多報酬

您是否為使用 .NET、C#、Java、Python 或 Node.js 的開發者建立內容?將您的專業知識轉化為額外收入!

Iron 支援團隊

我們線上24小時,每週5天。
聊天
電子郵件
給我打電話