Zum Fußzeileninhalt springen
Iron Academy Logo
Lernen Sie C#
Lernen Sie C#

Andere Kategorien

Asynchrones Zippen in .NET 10: Erstellen oder Extrahieren in einer Zeile

Tim Corey
~12m

ZIP-Dateioperationen in C# waren immer über System.IO.Compression möglich, jedoch waren alle Aufrufe synchron, was bedeutet, dass der Thread blockiert wurde, bis das Archiv vollständig geschrieben oder gelesen war. .NET 10 ändert dies mit einer Reihe von asynchronen Überladungen, die es Ihnen ermöglichen, Zip-Dateien zu erstellen, zu extrahieren und zu füllen, ohne den aufrufenden Thread zu blockieren.

Diese Anleitung demonstriert die neuen Async-Zip-Überladungen in .NET 10, basierend auf Tim Coreys aktuellem Leitfaden. Wir betrachten drei nachdetailliert Ansätze: ein Einzeiler zum Erstellen eines Archivs, ein einziger Aufruf zum Entpacken davon und eine manuelle Methode für volle Kontrolle, während wir auch ein Eingrenzen using Fallstrick untersuchen.

Einrichtung: Pfade und der Quellordner

[0:28 - 1:55] Die Einrichtung beginnt mit einer Konsolenanwendung, die auf .NET 10 abzielt und eine einzelne Using-Direktive verwendet:

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

Drei Zeichenfolgenvariablen definieren die im gesamten Demo verwendeten Pfade:

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

Das wörtliche Zeichenfolgenpräfix (@) vermeidet die Notwendigkeit, Rückwärtsschrägstriche zu verdoppeln. sourceDirectory ist der Ordner, der gezippt werden soll. destinationZipFile ist der vollständige Pfad für das Archiv, das erstellt wird. destinationDirectory ist der Ort, an dem der Inhalt nach der Extraktion landet.

Ein praktischer Hinweis: Zeigen Sie niemals destinationZipFile auf einen Pfad innerhalb von sourceDirectory. Das Schreiben des Zip in den Ordner, der gezippt wird, verursacht eine rekursive Leseschleife, die den Vorgang unterbricht.

Der Testordner enthält zwei Dateien im Root-Verzeichnis und eine dritte Datei in einem Unterordner, was bei der Erkundung der includeBaseDirectory Option und der relativen Pfadhandhabung im manuellen Ansatz von Bedeutung ist.

Erstellen eines Archivs in einer Zeile

[2:35 - 4:20] Der asynchrone Erstellungsaufruf ist ein direkter Ersatz für das synchrone ZipFile.CreateFromDirectory:

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

CompressionLevel.SmallestSize priorisiert die kleinste Ausgabe auf Kosten einer etwas längeren Verarbeitungszeit. CompressionLevel.Fastest macht das Gegenteil. Für die meisten Entwicklungsszenarien ist der Unterschied vernachlässigbar, aber auf einem Webserver, der viele Archive parallel verarbeitet, lohnt sich der Abwägung.

includeBaseDirectory kontrolliert, ob der Quellverzeichnisname als Root-Eintrag im Archiv erscheint. Mit false (dem Standard) öffnet sich das Zip direkt zu den Dateien. Beim Übergeben von true wird stattdessen ein test Ordner am Root platziert, mit den eigentlichen Dateien darin. Die meisten Anwendungsfälle profitieren davon, dies auf false zu setzen.

Extrahieren eines Archivs in einer Zeile

[4:45 - 5:55] Die Extraktion folgt dem gleichen Muster:

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

destinationDirectory wird automatisch erstellt, wenn es noch nicht existiert. Der overwriteFiles Parameter standardmäßig auf false, das eine Ausnahme wirft, wenn eine Datei im Archiv bereits am Zielpfad existiert. Stellen Sie es auf true ein, um vorhandene Dateien still zu ersetzen. Das zweimalige Ausführen der Extraktion veranschaulicht dies: Der erste Durchgang ist erfolgreich und erstellt den Ordner, während der zweite eine Ausnahme wirft, sofern overwriteFiles: true nicht angegeben ist.

Selektives Zippen: Dateien einzeln hinzufügen

[6:15 - 9:50] Der Einzeilenansatz zippt ein ganzes Verzeichnis ohne Filterung. Wenn Sie nur bestimmte Dateien einschließen müssen, erstellen Sie das Archiv manuell mit einem FileStream und 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);

Einige Parameter hier sind es wert, verstanden zu werden. FileMode.Create überschreibt jede vorhandene Datei an diesem Pfad. FileMode.CreateNew wirft stattdessen eine Ausnahme, wenn die Datei bereits existiert. FileShare.None sperrt die Datei exklusiv, während das Archiv geschrieben wird, was verhindert, dass andere Prozesse sie während der Operation lesen oder schreiben.

useAsync: true auf dem FileStream ermöglicht asynchrone E/A auf Betriebssystemebene. Beachten Sie, dass das Einstellen dieser Option ohne tatsächliche Nutzung von asynchronen Aufrufen weiter unten den Prozess erheblich verlangsamen kann, manchmal um das Zehnfache. Da die Eintragsschleife await verwendet, ist useAsync: true hier die richtige Wahl. Bei einem synchronen Codepfad lassen Sie es beim Standard false.

leaveOpen: false auf dem ZipArchive sagt ihm, den zugrunde liegenden Stream zu schließen und zu fluten, wenn er disponiert wird. entryNameEncoding: null behält die Standardcodierung bei, die laut Dokumentation empfohlen wird, es sei denn, Sie haben einen spezifischen Grund, sie zu ändern.

Mit dem archivfertigen Inhalt, rufen Sie die Quellinhalte ab und schreiben Sie jeden Eintrag:

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 mit SearchOption.AllDirectories ruft Dateien aus verschachtelten Unterordnern ab, wodurch die Unterordnerstruktur im Archiv erhalten bleibt. Das Musterargument ("*") ist, wo Sie nach Erweiterung filtern: "*.txt" würde das Archiv beispielweise auf Textdateien beschränken.

Path.GetRelativePath entfernt das absolute Präfix von jedem Dateipfad und lässt nur den Teil relativ zu sourceDirectory übrig. Dies ist das, was als Eintragsname im Zip gespeichert wird, was die Unterordnerhierarchie originalgetreu reproduziert. Wenn Sie stattdessen Path.GetFileName(filePath) übergeben, landet jeder Eintrag im Zip-Root, unabhängig von seinem ursprünglichen Standort. Das erzeugt ein flaches Archiv, birgt jedoch das Risiko einer Namenskollision, wenn zwei Einträge denselben Dateinamen in verschiedenen Unterordnern teilen.

Die Eingeschlossene Using-Falle

[9:50 - 11:30] Wenn Sie versuchen, einen Extraktionsaufruf nach dem manuellen Zip-Block hinzuzufügen, erhalten Sie eine Dateisperr-Ausnahme: The process cannot access the file because it is being used by another process. Dies passiert, weil die using-Anweisung auf zipStream Dateibereichs-Syntax verwendet, was bedeutet, dass der Stream bis zum Ende der Datei offen bleibt, anstatt sich beim Brace des Zip-Blocks zu schließen. Der Extraktionsversuch läuft, während die Sperre noch gehalten wird.

Die Konvertierung in eine geklammerte Form löst dieses Problem:

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

Das Einfügen der Zip-Arbeit in Klammern und das Entfernen des nachgestellten Semikolons begrenzt die Lebensdauer des Streams auf diesen expliziten Geltungsbereich. Sobald die Ausführung die schließende Klammer verlässt, wird die Sperre freigegeben und ein nachfolgender Extraktionsaufruf kann dieselbe Datei ohne Konflikte öffnen.

Abschluss

[11:40 - end] Die Einzeiler behandeln die häufigen Fälle: CreateFromDirectoryAsync für das Archivieren eines gesamten Ordners und ExtractToDirectoryAsync für das Entpacken. Wenn Sie Kontrolle darüber benötigen, welche Dateien ins Archiv gelangen, bietet der manuelle FileStream und ZipArchive Ansatz Filtern, Umbenennen und Pfad-Umstrukturierung auf Eintragsebene.

Um zusammenzufassen: Fügen Sie using System.IO.Compression hinzu, rufen Sie await ZipFile.CreateFromDirectoryAsync oder await ZipFile.ExtractToDirectoryAsync für einfache Fälle auf, und greifen Sie auf den manuellen ZipArchive Pfad zurück, wenn Sie Einträge filtern oder umbenennen müssen. Begrenzen Sie Ihre using Blöcke mit Klammern, wenn der Stream freigegeben werden muss, bevor nachfolgender Code ausgeführt wird. Diese Ergänzungen machen asynchrone Muster im gesamten Zip-Workflow in .NET 10 verfügbar.

Sehen Sie sich das vollständige Video auf Tim Coreys YouTube Kanal an, um den Live-Coding-Nachahmer zu folgen.

Hero Worlddot related to Asynchrones Zippen in .NET 10: Erstellen oder Extrahieren in einer Zeile
Hero Affiliate related to Asynchrones Zippen in .NET 10: Erstellen oder Extrahieren in einer Zeile

Verdienen Sie mehr, indem Sie teilen, was Sie lieben

Erstellen Sie Inhalte für Entwickler, die mit .NET, C#, Java, Python oder Node.js arbeiten? Verwandeln Sie Ihr Fachwissen in ein zusätzliches Einkommen!

Iron Support Team

Wir sind 24 Stunden am Tag, 5 Tage die Woche online.
Chat
E-Mail
Rufen Sie mich an