Async Zip dans .NET 10 : Créer ou extraire en une ligne
[[academy-video-youtube({"vid": "YgQ3ta6455A", "start_time": "0", "title": "Async Zip in .NET 10: One Line Create or Extract", "creator": "Tim Corey", "length": "~12m"})]]
Les opérations sur les fichiers Zip en C# ont toujours été possibles via System.IO.Compression, mais chaque appel était synchrone, ce qui signifie que le thread était bloqué jusqu'à ce que l'archive soit entièrement écrite ou lue. .NET 10 change cela avec un ensemble de surcharges asynchrones qui vous permettent de créer, extraire et remplir des fichiers zip sans occuper le thread appelant.
Cette démonstration explique les nouvelles surcharges zip asynchrones dans .NET 10, basées sur le récent guide de Tim Corey. Nous examinerons trois approches de plus en plus détaillées : une ligne unique pour créer une archive, un seul appel pour la décompresser, et une méthode manuelle pour un contrôle total, tout en examinant également un piège d'using en portée.
Configuration : Chemins et dossier source
[0:28 - 1:55] La configuration commence par une application console cible .NET 10 et une seule directive using :
using System.IO.Compression;
using System.IO.Compression;
Trois variables de chaîne définissent les chemins utilisés tout au long de la démo :
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";
Le préfixe de chaîne verbatim (@) évite d'avoir à doubler les barres obliques inverses. sourceDirectory est le dossier à compresser. destinationZipFile est le chemin complet pour l'archive qui sera créée. destinationDirectory est l'endroit où le contenu atterrira lorsqu'il sera extrait.
Un avertissement pratique : ne jamais pointer destinationZipFile vers un chemin à l'intérieur de sourceDirectory. L'écriture du zip dans le dossier en cours de zippage provoque une boucle de lecture récursive qui brise le processus.
Le dossier de test contient deux fichiers à la racine et un troisième fichier dans un sous-dossier, ce qui est important lors de l'exploration de l'option includeBaseDirectory et de la gestion des chemins relatifs dans l'approche manuelle.
Créer une archive en une ligne
[2:35 - 4:20] L'appel de création asynchrone est un remplacement direct pour le synchrone ZipFile.CreateFromDirectory :
await ZipFile.CreateFromDirectoryAsync(
sourceDirectory,
destinationZipFile,
CompressionLevel.SmallestSize,
includeBaseDirectory: false);
await ZipFile.CreateFromDirectoryAsync(
sourceDirectory,
destinationZipFile,
CompressionLevel.SmallestSize,
includeBaseDirectory: false);
CompressionLevel.SmallestSize privilégie la plus petite sortie au prix d'un peu plus de temps de traitement. CompressionLevel.Fastest fait l'inverse. Pour la plupart des scénarios de développement, la différence est négligeable, mais sur un serveur web traitant de nombreux archives simultanément, le compromis vaut la peine d'être évalué.
includeBaseDirectory contrôle si le nom du répertoire source apparaît comme une entrée racine à l'intérieur de l'archive. Avec false (la valeur par défaut), le zip s'ouvre directement sur les fichiers. Passer true place à la place un dossier test à la racine, avec les fichiers réels à l'intérieur. La plupart des cas d'utilisation bénéficient de mettre cela à false.
Extraire une Archive en Une Ligne
[4:45 - 5:55] L'extraction suit le même modèle :
await ZipFile.ExtractToDirectoryAsync(
destinationZipFile,
destinationDirectory,
overwriteFiles: false);
await ZipFile.ExtractToDirectoryAsync(
destinationZipFile,
destinationDirectory,
overwriteFiles: false);
destinationDirectory est créé automatiquement s'il n'existe pas déjà. Le paramètre overwriteFiles par défaut est false, ce qui lance une exception si un fichier quelconque de l'archive existe déjà au chemin cible. Réglez-le sur true pour remplacer les fichiers existants silencieusement. Exécuter l'extraction deux fois illustre cela : le premier passage réussit et crée le dossier, tandis que le second lance une exception à moins que overwriteFiles: true ne soit spécifié.
Zippage Sélectif : Ajouter des Fichiers Un par Un
[6:15 - 9:50] L'approche en une ligne zipe un répertoire entier sans filtrage. Lorsque vous avez besoin d'inclure uniquement des fichiers spécifiques, vous créez l'archive manuellement en utilisant un FileStream et 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);
Quelques paramètres ici valent la peine d'être compris. FileMode.Create remplace tout fichier existant à ce chemin. FileMode.CreateNew lance une exception si le fichier existe déjà. FileShare.None verrouille exclusivement le fichier pendant que l'archive est en cours d'écriture, empêchant ainsi d'autres processus de le lire ou de l'écrire pendant l'opération.
useAsync: true sur le FileStream permet des entrées/sorties asynchrones au niveau du système d'exploitation. Notez que définir cela sans utiliser réellement des appels async en aval peut ralentir considérablement le processus, parfois jusqu'à dix fois. Parce que la boucle d'écriture des entrées utilise await, useAsync: true est le choix correct ici. Sur un chemin de code synchrone, laissez-le à la valeur par défaut false.
leaveOpen: false sur le ZipArchive indique de fermer et de purger le flux sous-jacent lorsqu'il est libéré. entryNameEncoding: null garde le codage par défaut, que la documentation recommande de maintenir sauf si vous avez une raison spécifique de le changer.
Avec l'archive prête, récupérez le contenu source et écrivez chaque entrée :
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 avec SearchOption.AllDirectories récupère des fichiers à partir de sous-dossiers imbriqués, ce qui est la manière dont la structure du sous-dossier est préservée dans l'archive. L'argument de motif ("*") est l'endroit où vous filtrez par extension : "*.txt" limiterait l'archive aux fichiers texte, par exemple.
Path.GetRelativePath retire le préfixe absolu de chaque chemin de fichier, en ne laissant que la portion relative à sourceDirectory. C'est ce qui est stocké comme nom d'entrée à l'intérieur du zip, reproduisant fidèlement la hiérarchie des sous-dossiers. Si vous passez plutôt Path.GetFileName(filePath), chaque entrée atterrit à la racine du zip, quelle que soit son emplacement d'origine. Cela produit une archive plate, mais risque une collision de noms si deux entrées partagent un nom de fichier à travers différents sous-dossiers.
Le Piège de l'Utilisation Délimitée
[9:50 - 11:30] Si vous essayez d'ajouter un appel d'extraction après le bloc zip manuel, vous rencontrerez une exception de verrouillage de fichier : The process cannot access the file because it is being used by another process. Cela se produit parce que la instruction using sur zipStream utilise une syntaxe à portée de fichier, ce qui signifie que le flux reste ouvert jusqu'à la fin du fichier plutôt que de fermer à l'accolade du bloc zip. La tentative d'extraction s'exécute alors que le verrou est toujours maintenu.
Convertir en une forme avec accolades résout cela :
// 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
Enveloppant le travail de zip dans des accolades et retirant le point-virgule de fin, on limite la durée de vie du flux à cette portée explicite. Une fois que l'exécution quitte l'accolade de fermeture, le verrou est libéré et un appel d'extraction ultérieur peut ouvrir le même fichier sans conflit.
Conclusion
[11:40 - end] Les lignes uniques gèrent les cas courants : CreateFromDirectoryAsync pour archiver un dossier entier et ExtractToDirectoryAsync pour le décompresser. Lorsque vous avez besoin de contrôler quels fichiers entrent dans l'archive, l'approche manuelle FileStream et ZipArchive vous offre le filtrage, le renommage et la réécriture des chemins au niveau de l'entrée.
Pour récapituler : ajoutez using System.IO.Compression, appelez await ZipFile.CreateFromDirectoryAsync ou await ZipFile.ExtractToDirectoryAsync pour les cas simples, et optez pour le chemin manuel ZipArchive lorsque vous avez besoin de filtrer ou de renommer des entrées. Cadrez vos blocs using avec des accolades si le flux doit être libéré avant l'exécution du code suivant. Ces ajouts rendent les modèles async/await disponibles tout au long du flux de travail zip complet dans .NET 10.
Regardez la vidéo complète sur la chaîne de Tim Corey sur YouTube pour suivre le codage en direct.
