Przejdź do treści stopki
Iron Academy Logo
Naucz się C#
Naucz się C#

Inne Kategorie

Async Zip w .NET 10: Jedna Linia Tworzenie lub Wyodrębnianie

[[academy-video-youtube({"vid": "YgQ3ta6455A", "start_time": "0", "title": "Async Zip in .NET 10: One Line Create or Extract", "creator": "Tim Corey", "length": "~12m"})]]

Operacje na plikach zip w C# zawsze były możliwe dzięki System.IO.Compression, ale każde wywołanie było synchroniczne, co oznaczało, że proces blokował się, dopóki archiwum nie zostało w pełni zapisane lub odczytane. .NET 10 zmienia to dzięki zestawowi przeciążeń asynchronicznych, które pozwalają tworzyć, wyodrębniać i wypełniać pliki zip bez zajmowania wątku wywołującego.

Ten przewodnik demonstruje nowe asynchroniczne przeciążenia zip w .NET 10, bazując na ostatnim przewodniku Tima Coreya. Przyjrzymy się trzem coraz bardziej szczegółowym podejściom: jednolinijkowemu tworzeniu archiwum, jednemu wywołaniu do jego rozpakowania oraz ręcznej metodzie dla pełnej kontroli, jednocześnie badając problematyczne using.

Konfiguracja: Ścieżki i folder źródłowy

[0:28 - 1:55] Konfiguracja zaczyna się od aplikacji konsolowej celującej w .NET 10 i pojedynczej dyrektywy używania:

using System.IO.Compression;
using System.IO.Compression;

Trzy zmienne string definiują ścieżki używane w całym demie:

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

Prefiks ciągu dosłownego (@) eliminuje konieczność podwajania ukośników. sourceDirectory to folder do skompresowania. destinationZipFile to pełna ścieżka dla archiwum, które zostanie utworzone. destinationDirectory to miejsce, gdzie pliki zostaną umieszczone po wyodrębnieniu.

Jedno praktyczne ostrzeżenie: nigdy nie wskazuj destinationZipFile na ścieżkę w obrębie sourceDirectory. Zapisanie zip do folderu, który jest zipowany powoduje rekurencyjną pętlę odczytu, która przerywa proces.

Folder testowy zawiera dwa pliki w katalogu głównym i trzeci plik w podfolderze, co ma znaczenie przy eksplorowaniu opcji includeBaseDirectory i obsługi ścieżek względnych w podejściu ręcznym.

Tworzenie archiwum w jednej linii

[2:35 - 4:20] Asynchroniczne wywołanie tworzenia jest bezpośrednią zamianą dla synchronicznego ZipFile.CreateFromDirectory:

await ZipFile.CreateFromDirectoryAsync(
    sourceDirectory,
    destinationZipFile,
    CompressionLevel.SmallestSize,
    includeBaseDirectory: false);
await ZipFile.CreateFromDirectoryAsync(
    sourceDirectory,
    destinationZipFile,
    CompressionLevel.SmallestSize,
    includeBaseDirectory: false);

CompressionLevel.SmallestSize priorytetowo traktuje najmniejszy wynik kosztem nieco większego czasu przetwarzania. CompressionLevel.Fastest robi odwrotnie. Dla większości scenariuszy deweloperskich różnica jest znikoma, ale na serwerze web przetwarzającym wiele archiwów jednocześnie, warto ocenić ten kompromis.

includeBaseDirectory kontroluje, czy nazwa katalogu źródłowego pojawia się jako wpis główny w archiwum. Z false (domyślnie) zip otwiera się bezpośrednio na pliki. Przekazanie true zamiast tego powoduje umieszczenie folderu test w katalogu głównym, z rzeczywistymi plikami w jego wnętrzu. Większość scenariuszy korzysta z ustawienia tego na false.

Wyodrębnianie archiwum w jednej linii

[4:45 - 5:55] Wyodrębnianie śledzi ten sam wzorzec:

await ZipFile.ExtractToDirectoryAsync(
    destinationZipFile,
    destinationDirectory,
    overwriteFiles: false);
await ZipFile.ExtractToDirectoryAsync(
    destinationZipFile,
    destinationDirectory,
    overwriteFiles: false);

destinationDirectory jest tworzony automatycznie, jeśli wcześniej nie istnieje. Parametr overwriteFiles domyślnie ma wartość false, co powoduje wyjątek, jeśli jakikolwiek plik w archiwum już istnieje na docelowej ścieżce. Ustaw na true, aby zastąpić istniejące pliki bez żadnego powiadomienia. Dwukrotne wykonanie ekstrakcji ilustruje to: pierwsza próba zakończy się sukcesem i stworzy folder, podczas gdy druga wywołuje wyjątek, chyba że określony zostanie overwriteFiles: true.

Wyborcze zipowanie: Dodawanie plików po jednym

[6:15 - 9:50] Podejście jednowierszowe zipuje cały katalog bez filtrowania. Gdy należy uwzględnić tylko konkretne pliki, tworzy się archiwum ręcznie za pomocą FileStream i 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);

Kilka parametrów tutaj jest wartych zrozumienia. FileMode.Create zastępuje każdy istniejący plik na tej ścieżce. FileMode.CreateNew wywołuje wyjątek, jeśli plik już istnieje. FileShare.None blokuje plik wyłącznie na czas, gdy archiwum jest zapisywane, uniemożliwiając innym procesom odczyt lub zapis w trakcie operacji.

useAsync: true na FileStream umożliwia asynchroniczne I/O na poziomie systemu operacyjnego. Zauważ, że ustawienie tego bez rzeczywistego używania wywołań asynchronicznych w dół może znacznie spowolnić proces, czasami nawet dziesięć razy. Ponieważ pętla zapisywania wpisów używa await, useAsync: true jest tutaj właściwym wyborem. Na ścieżce kodu synchronicznego pozostaw domyślnie ustawioną wartość false.

leaveOpen: false na ZipArchive informuje o zamknięciu i wyczyszczeniu podkładowego strumienia podczas zwalniania zasobu. entryNameEncoding: null utrzymuje domyślne kodowanie, które dokumentacja zaleca pozostawić, chyba że istnieje konkretny powód do jego zmiany.

Gdy archiwum jest gotowe, pobierz treści źródłowe i zapisz każdy wpis:

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);
}

Directory.GetFiles z SearchOption.AllDirectories pobiera pliki z zagnieżdżonych podfolderów, co pozwala zachować strukturę podfolderów w archiwum. Argument wzorca ("*") to miejsce na filtrowanie według rozszerzenia: "*.txt" ograniczyłby archiwum do plików tekstowych, na przykład.

Path.GetRelativePath usuwa bezwzględny prefiks z każdej ścieżki pliku, pozostawiając tylko część względem sourceDirectory. To jest to, co jest przechowywane jako nazwa wpisu wewnątrz zip, wiernie powielając hierarchię podfolderu. Jeśli zamiast tego przekażesz Path.GetFileName(filePath), każdy wpis trafia do korzenia zipu niezależnie od jego pierwotnej lokalizacji. To tworzy płaskie archiwum, ale ryzykuje kolizję nazw, jeśli dwa wpisy dzielą nazwę pliku w różnych podfolderach.

Pułapka z użyciem zasięgowym

[9:50 - 11:30] Jeśli spróbujesz dodać wywołanie ekstrakcji po bloku ręcznego zipu, napotkasz wyjątek blokady pliku: The process cannot access the file because it is being used by another process. Dzieje się tak, ponieważ instrukcja using na zipStream używa składni ograniczonej do plików, co oznacza, że strumień pozostaje otwarty do końca pliku, zamiast zamykania się przy klamrze bloku zip. Próba ekstrakcji działa, gdy blokada nadal jest trzymana.

Konwersja do formy z nawiasami rozwiązuje to:

// 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

Opakowanie pracy zip w nawiasy i usunięcie końcowego średnika ogranicza czas życia strumienia do tego explicit zasięgu. Gdy wykonanie opuszcza zamykający nawias, blokada jest zwalniana, a kolejne wywołanie ekstrakcji może otworzyć ten sam plik bez konfliktu.

Wnioski

[11:40 - end] Jednolinijkowce obsługują typowe przypadki: CreateFromDirectoryAsync do archiwizacji całego folderu i ExtractToDirectoryAsync do jego rozpakowania. Gdy potrzebujesz kontroli nad tym, które pliki trafiają do archiwum, podejście ręczne FileStream i ZipArchive daje możliwość filtrowania, zmiany nazw i przerabiania ścieżek na poziomie wpisu.

Podsumujmy: dodaj using System.IO.Compression, wywołaj await ZipFile.CreateFromDirectoryAsync lub await ZipFile.ExtractToDirectoryAsync dla prostych przypadków, i skorzystaj z ręcznej ścieżki ZipArchive, kiedy musisz filtrować lub zmieniać nazwy wpisów. Ogranicz swoje bloki using klamrami, jeśli strumień musi zostać zwolniony przed uruchomieniem kolejnego kodu. Te dodatki czynią async/await wzorce dostępne na całej workflow zip w .NET 10.

Obejrzyj pełne wideo na kanale YouTube Tima Coreya channel, aby śledzić na żywo kodowanie.

Hero Worlddot related to Async Zip w .NET 10: Jedna Linia Tworzenie lub Wyodrębnianie
Hero Affiliate related to Async Zip w .NET 10: Jedna Linia Tworzenie lub Wyodrębnianie

Zarabiaj więcej, dzieląc się tym, co kochasz

Tworzysz treści dla deweloperów pracujących z .NET, C#, Java, Python, czy Node.js? Zamień swoją wiedzę specjalistyczną na dodatkowy dochód!

Zespół wsparcia Iron

Jesteśmy online 24 godziny, 5 dni w tygodniu.
Czat
E-mail
Zadzwoń do mnie