Async C#: Ein tiefer Einblick in Async Zip mit Tim Corey
Dateikomprimierung und -extraktion sind wesentliche Aufgaben für viele C#-Entwickler, ob Sie an einer Desktop-App arbeiten, an einem Webserver oder einfach Daten archivieren müssen. Seit Jahren konnten .NET-Entwickler Zip-Dateien synchron erstellen, aktualisieren und extrahieren, wodurch der UI-Thread blockiert oder andere Operationen verzögert wurden. Wie Tim Corey in seinem Video "Async Zip in .NET 10 – One Line Create or Extract And More" erklärt, führt .NET 10 asynchrone Vorgänge für Zip-Dateien ein, sodass Entwickler effizienten Code schreiben können, der E/A-gebundene Operationen ausführt, ohne die Hauptanwendung einzufrieren.
Asynchrones Programmieren in C# basiert hauptsächlich auf Task- und Task-Objekten, wobei die Task-Klasse als Kernkomponente dient, um laufende Arbeiten zu modellieren und gleichzeitige Ausführung zu ermöglichen. Das Task-asynchrone Programmiermodell (TAP) bietet eine Abstraktionsebene über die typische asynchrone Kodierung, die es erleichtert, asynchrone Operationen und Ausnahmen zu handhaben. Die System.Threading.Tasks.Task-Klasse und verwandte Typen werden verwendet, um das aufgabenbasierte asynchrone Programmiermodell in C# zu implementieren.
In diesem Artikel folgen wir Tims Erklärung und zeigen, wie man asynchrone Operationen in C# ausführt, um Dateien zu erstellen, zu extrahieren und selektiv zu zippen, und dabei das async/await-Muster, Task-Objekte und ordnungsgemäße Fehlerbehandlung verwendet.
Einführung in das Asynchrone Programmieren und Async Zip in .NET 10
Tim beginnt mit dem Hinweis auf die langjährige Fähigkeit von C#, Zip-Dateien zu erstellen und zu extrahieren. Mit .NET 10 können Entwickler jedoch jetzt asynchrone Code verwenden, um diese Aufgaben zu bewältigen, ohne den aufrufenden Thread zu blockieren. Durch die Verwendung asynchroner Methoden und des await-Schlüsselworts können Operationen wie das Erstellen oder Extrahieren großer Zip-Dateien auf Hintergrund-Threads ausgeführt werden, sodass der Haupt-UI-Thread reaktionsfähig bleibt.
Das async-Schlüsselwort ist nur ein Dekorator, der dem C#-Compiler mitteilt, dass die Methode mindestens ein Vorkommen des await-Schlüsselworts enthält. Das await-Schlüsselwort kann nur innerhalb einer async-Methode verwendet werden, und es muss einem Task- oder Task
Wie Tim erklärt, ist dies besonders wichtig in Anwendungen, die mehrere Aufgaben ausführen, wie Netzwerkaufrufe, Datenbankabfragen oder Datei-I/O, bei denen das Warten auf Aufgaben es den CPU-gebundenen Aufgaben ermöglicht, ohne Blockieren des aufrufenden Threads fortzufahren. Das Blockieren von asynchronem Code mit .Result oder .Wait() verwandelt asynchronen Code wieder in synchronen Code, was zu Deadlocks führen kann.
Asynchrone Methoden geben Task- oder Task
Projekt Einrichten
Tim beginnt mit dem Öffnen von Visual Studio 2026 und dem Erstellen einer Konsolenanwendung namens ZipFilesApp. Er stellt fest, dass auch statische async-Task-Main-Methoden verwendet werden können, um asynchrone Aufgaben im Einstiegspunkt des Programms auszuführen, ohne den Hauptthread zu blockieren. Beim Anpassen vorhandenen Codes können Sie häufig synchronen Code in Tasks einwickeln oder asynchrone Varianten implementieren, um asynchrones Verhalten einzuführen, ohne den gesamten Code neu schreiben zu müssen.
Er fügt dann die wesentliche using-Direktive hinzu:
using System.IO.Compression;
Dies ermöglicht den Zugriff auf asynchrone Methoden wie CreateFromDirectoryAsync und ExtractToDirectoryAsync. Beim Erstellen von Aufrufmethoden ist es wichtig, konsistente Namenskonventionen zu verwenden, wie z. B. das Anhängen des 'Async'-Suffix an asynchrone Methoden, um sie klar von synchronen zu unterscheiden. Inkonsistente Methodennamen können zu Verwirrung führen, ob eine Methode synchron oder asynchron ist.
Als nächstes bereitet Tim drei Zeichenfolgenvariablen vor, um die für Zip-Operationen erforderlichen Pfade zu definieren:
-
Quellverzeichnis – Der Ordner zum Komprimieren.
-
Ziel-Zip-Datei – Der vollständige Pfad, um die Zip-Datei zu speichern.
- Zielverzeichnis – Der Ordner, in dem extrahierte Dateien gespeichert werden.
Tim erwähnt die Verwendung des @-Symbols in Zeichenfolgenliteralen, um das Escaping von Backslashes zu vermeiden, was ein häufiges Problem in synchronem Code beim Schreiben von Windows-Pfaden ist. Im gesamten Artikel werden Codebeispiele und Codeausschnitte bereitgestellt, um zu veranschaulichen, wie asynchrones Programmieren in C# implementiert wird.
Erstellen einer Zip-Datei mit einer asynchronen Methode
Um 3:47 demonstriert Tim eine einzeilige asynchrone Operation mithilfe einer statischen async-Task-Methode:
await ZipFile.CreateFromDirectoryAsync(
sourceDirectory,
destinationZipFile,
CompressionLevel.SmallestSize,
includeBaseDirectory: false
);
Hier erklärt Tim mehrere wichtige Aspekte:
-
Das async-Schlüsselwort kennzeichnet diese asynchrone Methode und ermöglicht die Verwendung von await.
-
await pausiert die Ausführung dieser asynchronen Methode, bis die Aufgabe abgeschlossen ist, sodass andere Aufgaben oder der Haupt-UI-Thread weiterhin ausgeführt werden können. Dies zeigt, wie await zusammenarbeitet und die Ausführung pausiert, um nicht blockierendes Verhalten in asynchronen Operationen wie Datei-I/O sicherzustellen.
-
CompressionLevel.SmallestSize sorgt für eine effiziente Komprimierung.
- Der includeBaseDirectory-Parameter steuert, ob der Stammordner im Zip enthalten ist.
Dieses Beispiel zeigt, wie der asynchrone Ablauf in C# durch die Async- und Await-Schlüsselwörter verwaltet wird, was den Code wartbarer macht und die Ausnahmebehandlung vereinfacht. Das await-Schlüsselwort kann nur innerhalb einer asynchronen Methode verwendet werden, wie einer öffentlichen async-Task-Methode. Wenn ein await auftritt, generiert der C#-Compiler eine Zustandsmaschine, um den Ausführungsablauf zu verwalten, pausiert die Ausführung, startet die asynchrone Aufgabe und setzt sie fort, sobald diese abgeschlossen ist. Wenn die asynchrone Methode einen Wert zurückgibt, kann das Ergebnis in einer Variablen erfasst werden, wie int result, zur späteren Verwendung. Durch die Verwendung von async/await müssen Entwickler keine Hintergrundthreads oder Aufgaben im Threadpool manuell erstellen, da der Compiler die erforderliche Zustandsmaschine und den unterstützenden Code automatisch generiert.
Extrahieren einer Zip-Datei mit await Task asynchron
Tim geht zum Extrahieren einer Zip-Datei mit asynchronen Operationen über, indem er eine andere asynchrone Methode verwendet:
await ZipFile.ExtractToDirectoryAsync(
destinationZipFile,
destinationDirectory,
overwriteFiles: false
);
Er weist darauf hin, dass der overwriteFiles-Parameter die Ausnahmebehandlung übernimmt, falls der Ordner bereits existiert. Standardmäßig schlägt die Extraktion fehl, wenn Dateien bereits vorhanden sind, aber das Setzen auf true ermöglicht es der asynchronen Operation, vorhandene Dateien zu ersetzen.
Beim Arbeiten mit asynchronem Code ist robuste Fehlerbehandlung entscheidend. Tim betont die Bedeutung von try-catch-Blöcken zur Behandlung von Ausnahmen in asynchronen Methoden. Eine unsachgemäße Behandlung von Ausnahmen im asynchronen Code kann zu stillen Ausfällen oder unerwarteten Abstürzen führen, da Ausnahmen, die in asynchronen Aufgaben ausgelöst werden, möglicherweise nicht sofort sichtbar sind. Behandeln Sie immer Ausnahmen, um ein zuverlässiges Anwendungsverhalten zu gewährleisten.
Tim stellt fest, dass dieser asynchrone Code sicher für mehrere Aufgaben ist, was bedeutet, dass er keine anderen Anfragen oder Netzwerkaufrufe blockiert, die gleichzeitig stattfinden. Mit dem await-Schlüsselwort wird der aufrufende Thread freigegeben, bis die Aufgabe abgeschlossen ist, wodurch ein reaktionsfähiges Programmablauf geschaffen wird.
Darüber hinaus ist es wichtig, async void nur für Ereignis-Handler, wie Button-Klicks in GUI-Anwendungen, zu verwenden, wenn asynchrone Methoden definiert werden. In diesen Fällen enthält die Methodensignatur typischerweise (object sender, EventArgs e), wobei object sender die Quelle des Ereignisses identifiziert. Die Verwendung von async void außerhalb von Ereignis-Handlern kann zu unvorhersehbarem Verhalten und schwer diagnostizierbaren Fehlern führen, daher bevorzugen Sie async Task für die meisten asynchronen Methoden.
Selektives Hinzufügen von Dateien zu einem Zip
Manchmal benötigen Entwickler mehr Kontrolle, als nur einen gesamten Ordner zu zippen. Tim zeigt, wie asynchrone Operationen selektiv ausgeführt werden, indem er einen FileStream erstellt:
await using FileStream zipStream = new FileStream(
destinationZipFile,
FileMode.Create,
FileAccess.Write,
FileShare.None,
bufferSize: 4096,
useAsync: true
);
Beim asynchronen Hinzufügen von Dateien stellt jede Datei beim Hinzufügen eine spezifische Aufgabe dar. Indem Sie alle Aufgaben für das Hinzufügen von Dateien gleichzeitig starten und dann mit Methoden wie Task.WhenAll auf deren Abschluss warten, können Sie die Leistung erheblich verbessern. Das Verwalten laufender Aufgaben auf diese Weise ist von entscheidender Bedeutung für Hochkonkurrenzszenarien, beispielsweise wenn ein Server mehrere Anfragen gleichzeitig verarbeiten muss. Dieser Ansatz wird auch häufig bei der Interaktion mit einem Remote-Server verwendet, sodass Anwendungen während des Wartens auf Daten reaktionsfähig bleiben.
Tim erklärt jedes Detail:
-
FileMode.Create – Stellt sicher, dass eine neue Zip-Datei erstellt oder überschrieben wird.
-
FileAccess.Write – Ermöglicht das Schreiben in die Datei.
-
FileShare.None – Verhindert, dass andere Aufgaben auf die Datei zugreifen, während sie geschrieben wird.
- useAsync: true – Ermöglicht asynchrones Dateischreiben, einen Kern der async-Operation.
Er stellt fest, dass asynchrones Programmieren in diesem Kontext das Blockieren des Hauptthreads vermeidet, während die Aufgabe abgeschlossen wird, was besonders nützlich auf Webservern ist, die mehrere Aufgaben gleichzeitig bearbeiten. Hochkonkurrenzserver nutzen asynchrones Programmieren, um mehrere gleichzeitig laufende Clientanfragen effizient zu bearbeiten, ohne für jede Verbindung einen neuen Thread zu erzeugen. Das sequentielle Schreiben von asynchronem Code kann jedoch das Lesen und Debuggen erschweren, da asynchrone Aufgaben möglicherweise außerhalb der Reihenfolge abgeschlossen werden.
Erstellen des Zip-Archivs und Hinzufügen von Dateien
Als Nächstes erstellt Tim ein ZipArchive asynchron:
using ZipArchive archive = await ZipArchive.CreateAsync(
zipStream,
ZipArchiveMode.Create,
leaveOpen: false,
entryNameEncoding: null
);
-
Der await-Ausdruck stellt sicher, dass die Ausführung pausiert, bis die Aufgabe abgeschlossen ist, wodurch es sicher ist, mit dem Hinzufügen von Dateien fortzufahren. Async und await vereinfachen das Management von asynchronen Operationen, was den Code im Vergleich zu herkömmlichen rückrufbasierten Ansätzen leichter lesbar und wartbar macht.
-
Es ist wichtig zu beachten, dass derselbe Code je nach Synchronisationskontext unterschiedlich verhalten kann. Zum Beispiel kann in WPF- oder WinForms-Anwendungen das Fortschalten nach einem Await-Aufruf im Haupt-UI-Thread laufen, während in Konsolenanwendungen kein solcher Kontext existiert. Dies kann beeinflussen, wie Ihr asynchroner Code ausgeführt wird.
-
Wenn Sie Bibliothekscode schreiben, sollten Sie in Erwägung ziehen, ConfigureAwait(false) nach einem Await-Aufruf zu verwenden, um unnötige Kontextwechsel zu vermeiden und die Leistung zu verbessern.
- Mit Path.GetRelativePath erklärt Tim, wie man die Ordnerstruktur innerhalb des Zip-Archivs beibehält, was wesentlich ist, wenn asynchrone Arbeitsabläufe mit mehreren Dateien durchgeführt werden:
foreach (string filePath in Directory.GetFiles(sourceDirectory, "*", SearchOption.AllDirectories))
{
string relativePath = Path.GetRelativePath(sourceDirectory, filePath);
await archive.CreateEntryFromFileAsync(filePath, relativePath).ConfigureAwait(false);
}
Dies zeigt asynchronen Code in der Praxis, wo jedes Hinzufügen einer Datei ein asynchroner Aufruf ist, und das await-Schlüsselwort sorgt für die richtige Abfolge, während dennoch nicht blockierendes I/O durchgeführt wird. Asynchroner Code ermöglicht es dem aktuellen Thread, unblocked zu bleiben, die Reaktionsfähigkeit zu verbessern und dem Thread die Durchführung anderer Arbeiten zu gestatten, während auf den Abschluss von I/O-Operationen gewartet wird.
Umgang mit Dateizugriff und Ausnahmebehandlung
Tim hebt ein häufiges Szenario der Ausnahmebehandlung hervor: Der Versuch, ein Zip zu extrahieren, bevor die ursprüngliche FileStream-Aufgabe abgeschlossen ist. Die Verwendung von asynchronen void-Methoden für dateibeschränkte oder asynchronen Task-Methoden ohne ordnungsgemäße dateibeschränkte using-Anweisungen kann zu: führen
Der Prozess kann nicht auf die Datei zugreifen, da sie von einem anderen Prozess verwendet wird.
Die Lösung besteht darin, die asynchrone Operation in einem traditionellen dateibeschränkten using-Block zu umhüllen, um Ressourcen vor der Extraktion freizugeben:
await using (FileStream zipStream = new FileStream(...))
{
using ZipArchive archive = await ZipArchive.CreateAsync(zipStream, ...);
// Dateien asynchron hinzufügen
}
// Jetzt ist es sicher zu extrahieren
Dies stellt sicher, dass das Task-Objekt abgeschlossen wird und den Synchronisationskontext freigibt, um Datei-Sperrfehler zu vermeiden.
Fazit: Effizientes asynchrones Zip in .NET 10
Tim kommt zu dem Schluss, dass asynchrone Zip-Operationen in .NET 10 es Entwicklern ermöglichen, asynchrone Operationen effizient auszuführen, Blockierungen des Hauptthreads zu vermeiden und sich nahtlos in andere Aufgaben wie Netzwerkaufrufe oder Datenbankabfragen zu integrieren.
Mit statischen async-Task-Methoden, async-Modifikatoren und await-Ausdrücken können Entwickler:
-
Zip-Dateien komprimieren oder extrahieren, ohne den UI-Thread einzufrieren.
-
Mehrere Threads sicher handhaben, ohne eine manuelle Threadpool-Verwaltung.
-
Selektives Zipping durchführen und trotzdem die Ordnerstruktur beibehalten.
- Eine ordnungsgemäße Ausnahmebehandlung und Ressourcenbereinigung sicherstellen.
Tims Beispiele zeigen, dass async/await in Dateioperationen nicht nur praktisch ist – es ist ein wesentlicher Bestandteil des modernen asynchronen Programmierens in C#.
Indem Sie seinem Video folgen, können Entwickler effizienten Code schreiben, indem sie asynchrone Methoden, await-Aufrufe und asynchrone Arbeitsabläufe verwenden, um sowohl einfache als auch komplexe Dateikomprimierungsaufgaben nahtlos zu bewältigen.
