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

Tim Corey
~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 significaba 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 asincrónicas de zip en .NET 10, basada en la guía reciente de Tim Corey. Veremos tres enfoques progresivamente detallados: una línea para construir un archivo, una sola llamada para desempaquetarlo y un método manual para control total, al mismo tiempo que examinamos una trampa enfocada 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 textual (@) evita la necesidad de duplicar barras invertidas. sourceDirectory es la carpeta a comprimir. destinationZipFile es la ruta completa para el archivo que se creará. destinationDirectory es donde aterrizarán los contenidos cuando se extraigan.

Una advertencia práctica: nunca apuntes 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 que importa al explorar la opción includeBaseDirectory y el manejo de rutas relativas en el enfoque manual.

Crear un Archivo en Una Línea

[2:35 - 4:20] La llamada de creación asincrónica es un reemplazo directo para el sincronizado ZipFile.CreateFromDirectory:

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

CompressionLevel.SmallestSize prioriza el menor tamaño de salida a costa de un poco más de tiempo de procesamiento. CompressionLevel.Fastest hace lo contrario. 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 origen aparece como una entrada raíz dentro del archivo. Con false (el 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 actuales dentro de ella. La mayoría de los casos de uso se benefician de configurar 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 no existe ya. El parámetro overwriteFiles predetermina a false, lo que arroja una excepción si algún archivo en el archivo ya existe en la ruta de destino. Configúralo 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 arroja 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 necesitas incluir solo archivos específicos, construyes 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 arroja en su lugar si el archivo ya existe. FileShare.None bloquea el archivo exclusivamente mientras el archivo se escribe, evitando que otros procesos lo lean o escriban durante la operación.

useAsync: true en el FileStream permite I/O asincrónico 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 usa await, useAsync: true es la elección correcta aquí. En una ruta de código sincrónica, déjalo en el valor predeterminado false.

leaveOpen: false en el ZipArchive le dice que cierre y vacíe el flujo subyacente cuando se desecha. entryNameEncoding: null mantiene la codificación predeterminada, que la documentación recomienda dejar a menos que tengas 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, lo cual es cómo se preserva la estructura de subcarpetas en el archivo. El argumento del patrón ("*") es donde filtras por extensión: "*.txt" delimitarí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 pasas 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 intentas añadir una llamada de extracción después del bloque de zip manual, encontrarás 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 de using en zipStream usa sintaxis de alcance de archivo, lo que significa que el flujo permanece abierto hasta el final del archivo en lugar de cerrarse en la llave del bloque de 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 únicas manejan los casos comunes: CreateFromDirectoryAsync para archivar una carpeta completa y ExtractToDirectoryAsync para desempaquetarla. Cuando necesitas controlar qué archivos entran en el archivo, el enfoque manual FileStream y ZipArchive te brinda filtrado, renombrado y reescritura de rutas a nivel de entrada.

Para resumir: añade using System.IO.Compression, llama await ZipFile.CreateFromDirectoryAsync o await ZipFile.ExtractToDirectoryAsync para casos sencillos, y opta por el enfoque manual ZipArchive cuando necesites filtrar o renombrar entradas. Configura tus bloques using con llaves si el flujo debe liberarse antes de que el código subsiguiente se ejecute. 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