Saltar al pie de página
Iron Academy Logo
Aprender C#
Aprender C#

Otras categorías

Zip asincrónico en .NET 10: Crear o extraer en una línea

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

Las operaciones de archivos ZIP en C# siempre han sido posibles a través de System.IO.Compression, pero cada llamada era sincrónica, lo que significa que el hilo se bloqueaba hasta que el archivo se escribía o leía completamente. .NET 10 cambia esto con un conjunto de sobrecargas asíncronas que te permiten crear, extraer y poblar archivos zip sin ocupar el hilo que llama.

Este recorrido demuestra las nuevas sobrecargas de zip asíncronas en .NET 10, basado en la reciente guía de Tim Corey. Veremos tres enfoques progresivamente detallados: una sola línea para construir un archivo, una única llamada para descomprimirlo, y un método manual para tener control total, examinando también una trampa de ámbito using.

Configuración: Rutas y la Carpeta de Fuente

[0:28 - 1:55] La configuración comienza con una aplicación de consola dirigida a .NET 10 y una única directiva using:

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

Tres variables de cadena definen las rutas utilizadas durante la demostración:

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

El prefijo de cadena literal (@) evita la necesidad de duplicar las barras invertidas. sourceDirectory es la carpeta a comprimir. destinationZipFile es la ruta completa para el archivo que se creará. destinationDirectory es donde irán a parar los contenidos cuando se extraigan.

Una advertencia práctica: nunca apunte destinationZipFile a una ruta dentro de sourceDirectory. Escribir el zip en la carpeta que se está comprimiendo causa un bucle de lectura recursiva que rompe el proceso.

La carpeta de prueba contiene dos archivos en la raíz y un tercer archivo dentro de una subcarpeta, lo cual es importante cuando se explora la opción includeBaseDirectory y la gestión de rutas relativas en el enfoque manual.

Crear un Archivo en Una Línea

[2:35 - 4:20] La llamada de creación asíncrona es un reemplazo directo para el sincrónico ZipFile.CreateFromDirectory:

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

CompressionLevel.SmallestSize prioriza la salida más pequeña a costa de un poco de tiempo de procesamiento extra. CompressionLevel.Fastest hace lo opuesto. Para la mayoría de los escenarios de desarrollo la diferencia es insignificante, pero en un servidor web procesando muchos archivos simultáneamente, vale la pena evaluar el intercambio.

includeBaseDirectory controla si el nombre del directorio de origen aparece como una entrada raíz dentro del archivo. Con false (el valor predeterminado), el zip se abre directamente a los archivos. Pasar true en su lugar coloca una carpeta test en la raíz, con los archivos reales dentro de ella. La mayoría de los casos de uso se benefician de establecer esto en false.

Extraer un Archivo en Una Línea

[4:45 - 5:55] La extracción sigue el mismo patrón:

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

destinationDirectory se crea automáticamente si aún no existe. El parámetro overwriteFiles por defecto es false, lo cual lanza una excepción si cualquier archivo en el archivo ya existe en la ruta de destino. Establecerlo en true para reemplazar archivos existentes silenciosamente. Ejecutar la extracción dos veces ilustra esto: la primera pasada tiene éxito y crea la carpeta, mientras que la segunda lanza una excepción a menos que se especifique overwriteFiles: true.

Compresión selectiva: Añadiendo archivos uno por uno

[6:15 - 9:50] El enfoque de una sola línea comprime un directorio completo sin filtrar. Cuando necesite incluir solo archivos específicos, construya el archivo manualmente usando un FileStream y 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);

Aquí hay algunos parámetros que valen la pena entender. FileMode.Create sobrescribe cualquier archivo existente en esa ruta. FileMode.CreateNew lanza una excepción en su lugar si el archivo ya existe. FileShare.None bloquea el archivo de forma exclusiva mientras se escribe el archivo, evitando que otros procesos lo lean o escriban en él durante la operación.

useAsync: true en el FileStream habilita la E/S asíncrona a nivel del sistema operativo. Tenga en cuenta que establecer esto sin realmente usar llamadas async más adelante puede ralentizar el proceso significativamente, a veces hasta diez veces. Debido a que el bucle de escritura de entradas utiliza await, useAsync: true es la opción correcta aquí. En una ruta de código sincrónico, déjelo en el valor predeterminado false.

leaveOpen: false en el ZipArchive le indica que cierre y vacíe el flujo subyacente cuando se deseche. entryNameEncoding: null mantiene la codificación predeterminada, que la documentación recomienda dejar a menos que tenga una razón específica para cambiarla.

Con el archivo listo, recupere el contenido fuente y escriba cada entrada:

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 con SearchOption.AllDirectories recupera archivos de subcarpetas anidadas, que es como se preserva la estructura de las subcarpetas en el archivo. El argumento del patrón ("*") es donde se filtra por extensión: "*.txt" limitaría el archivo a archivos de texto, por ejemplo.

Path.GetRelativePath elimina el prefijo absoluto de cada ruta de archivo, dejando solo la parte relativa a sourceDirectory. Esto es lo que se almacena como el nombre de la entrada dentro del zip, reproduciendo fielmente la jerarquía de subcarpetas. Si en su lugar pasa Path.GetFileName(filePath), cada entrada aterriza en la raíz del zip independientemente de su ubicación original. Eso produce un archivo plano, pero corre el riesgo de una colisión de nombres si dos entradas comparten un nombre de archivo a través de diferentes subcarpetas.

La trampa de Using con ámbito

[9:50 - 11:30] Si intenta agregar una llamada de extracción después del bloque de zip manual, encontrará una excepción de bloqueo de archivo: The process cannot access the file because it is being used by another process. Esto ocurre porque la declaración using en zipStream utiliza la sintaxis delimitada por archivo, lo que significa que el flujo permanece abierto hasta el final del archivo en lugar de cerrarse al final de la llave del bloque zip. El intento de extracción se ejecuta mientras el bloqueo todavía está presente.

Convertirlo a una forma encerrada entre llaves soluciona esto:

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

Encerrar el trabajo de compresión en llaves y eliminar el punto y coma final limita la vida útil del flujo a ese ámbito explícito. Una vez que la ejecución sale de la llave de cierre, se libera el bloqueo y una llamada de extracción subsiguiente puede abrir el mismo archivo sin conflicto.

Conclusión

[11:40 - end] Las líneas individuales manejan los casos comunes: CreateFromDirectoryAsync para archivar una carpeta entera y ExtractToDirectoryAsync para descomprimirla. Cuando necesita control sobre qué archivos entran en el archivo, el enfoque manual FileStream y ZipArchive le ofrece filtrado, renombrado y reescritura de rutas a nivel de entrada.

Para resumir: agregue using System.IO.Compression, llame a await ZipFile.CreateFromDirectoryAsync o await ZipFile.ExtractToDirectoryAsync para casos sencillos, y recurra a la ruta manual ZipArchive cuando necesite filtrar o renombrar entradas. Delimite sus bloques using con llaves si el flujo debe liberarse antes de que se ejecute el código subsiguiente. Estas adiciones hacen que los patrones async/await estén disponibles a lo largo de todo el flujo de trabajo de zip en .NET 10.

Mira el video completo en el canal de YouTube de Tim Corey para seguir el desarrollo en vivo.

Hero Worlddot related to Zip asincrónico en .NET 10: Crear o extraer en una línea
Hero Affiliate related to Zip asincrónico en .NET 10: Crear o extraer en una línea

Gana más compartiendo lo que te gusta

¿Creas contenidos para desarrolladores que trabajan con .NET, C#, Java, Python o Node.js? ¡Convierte tu experiencia en un ingreso extra!

Equipo de soporte de Iron

Estamos disponibles online las 24 horas, 5 días a la semana.
Chat
Email
Llámame