Ir para o conteúdo do rodapé
Iron Academy Logo
Aprenda C#
Aprenda C#

Outras categorias

Async Zip no .NET 10: Criação ou Extração de Uma Linha

Tim Corey
~12m

As operações de arquivo Zip em C# sempre foram possíveis através de System.IO.Compression, mas cada chamada era síncrona, o que significa que o thread era bloqueado até que o arquivo fosse totalmente escrito ou lido. .NET 10 muda isso com um conjunto de sobrecargas assíncronas que permitem criar, extrair e popular arquivos zip sem prender o thread chamador.

Este tutorial demonstra as novas sobrecargas de zip assíncronas no .NET 10, baseado no guia recente de Tim Corey. Vamos observar três abordagens detalhadas progressivas: uma linha para construir um arquivo, uma chamada única para descompactá-lo e um método manual para controle total, enquanto também examinamos um problema de escopo de using.

Configuração: Caminhos e a Pasta de Origem

[0:28 - 1:55] A configuração começa com um aplicativo de console direcionado a .NET 10 e uma única diretiva usando:

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

Três variáveis de string definem os caminhos usados durante a demonstração:

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

O prefixo de string literal (@) evita a necessidade de duplicar barras invertidas. sourceDirectory é a pasta a ser compactada. destinationZipFile é o caminho completo para o arquivo que será criado. destinationDirectory é onde o conteúdo será colocado ao ser extraído.

Um aviso prático: nunca aponte destinationZipFile para um caminho dentro de sourceDirectory. Escrever o zip na pasta que está sendo compactada causa um loop de leitura recursiva que quebra o processo.

A pasta de teste contém dois arquivos na raiz e um terceiro arquivo dentro de uma subpasta, o que importa ao explorar a opção includeBaseDirectory e o manuseio de caminhos relativos na abordagem manual.

Criando um Arquivo em Uma Linha

[2:35 - 4:20] A chamada de criação assíncrona é uma substituição direta para o síncrono ZipFile.CreateFromDirectory:

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

CompressionLevel.SmallestSize prioriza o menor tamanho de saída ao custo de um pouco mais de tempo de processamento. CompressionLevel.Fastest faz o inverso. Para a maioria dos cenários de desenvolvimento, a diferença é insignificante, mas em um servidor web processando muitos arquivos ao mesmo tempo, a troca precisa ser avaliada.

includeBaseDirectory controla se o nome do diretório de origem aparece como uma entrada raiz dentro do arquivo. Com false (o padrão), o zip é aberto diretamente para os arquivos. Passar true em vez disso, coloca uma pasta test na raiz, com os arquivos reais dentro dela. A maioria dos casos de uso se beneficia configurando isso para false.

Extraindo um Arquivo em Uma Linha

[4:45 - 5:55] A extração segue o mesmo padrão:

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

destinationDirectory é criado automaticamente se ainda não existir. O parâmetro overwriteFiles por padrão é false, o que lança uma exceção se qualquer arquivo no arquivo já existir no caminho de destino. Configure para true para substituir arquivos existentes sem aviso. Executar a extração duas vezes ilustra isso: a primeira passagem é bem-sucedida e cria a pasta, enquanto a segunda lança uma exceção, a menos que overwriteFiles: true seja especificado.

Compressão Seletiva: Adicionando Arquivos Um por Um

[6:15 - 9:50] A abordagem de uma linha compacta um diretório inteiro sem filtragem. Quando você precisa incluir apenas arquivos específicos, você constrói o arquivo manualmente usando um FileStream e 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);

Alguns parâmetros aqui valem a pena ser compreendidos. FileMode.Create sobrescreve qualquer arquivo existente nesse caminho. FileMode.CreateNew lança em vez disso se o arquivo já existir. FileShare.None bloqueia o arquivo exclusivamente enquanto o arquivo está sendo escrito, impedindo que outros processos o leiam ou escrevam durante a operação.

useAsync: true no FileStream habilita E/S assíncrona no nível do sistema operacional. Observe que definir isso sem realmente usar chamadas assíncronas a jusante pode desacelerar o processo significativamente, às vezes em até dez vezes. Porque o loop de escrita de entrada usa await, useAsync: true é a escolha correta aqui. Em um caminho de código síncrono, deixe no padrão false.

leaveOpen: false no ZipArchive informa para fechar e liberar o stream subjacente quando for descartado. entryNameEncoding: null mantém a codificação padrão, que a documentação recomenda deixar a menos que você tenha um motivo específico para alterá-la.

Com o arquivo pronto, recupere o conteúdo de origem e escreva 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 com SearchOption.AllDirectories recupera arquivos de subpastas aninhadas, que é como a estrutura da subpasta é preservada no arquivo. O argumento do padrão ("*") é onde você filtra por extensão: "*.txt" limitaria o arquivo a arquivos de texto, por exemplo.

Path.GetRelativePath remove o prefixo absoluto de cada caminho de arquivo, deixando apenas a parte relativa a sourceDirectory. Isso é o que é armazenado como o nome da entrada dentro do zip, reproduzindo fielmente a hierarquia de subpastas. Se você passar Path.GetFileName(filePath), cada entrada vai para a raiz do zip, independentemente de sua localização original. Isso produz um arquivo plano, mas corre o risco de uma colisão de nomes se duas entradas compartilharem um nome de arquivo em diferentes subpastas.

A Armadilha do Using Escopado

[9:50 - 11:30] Se você tentar adicionar uma chamada de extração após o bloco de zip manual, você encontrará uma exceção de bloqueio de arquivo: The process cannot access the file because it is being used by another process. Isso acontece porque a declaração using em zipStream usa sintaxe de escopo de arquivo, o que significa que o stream permanece aberto até o final do arquivo, em vez de fechar na chave do bloco zip. A tentativa de extração é executada enquanto o bloqueio ainda está ativo.

Converter para uma forma com chave resolve isso:

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

Colocar o trabalho de compactação entre chaves e remover o ponto e vírgula final limita a vida útil do stream a esse escopo explícito. Uma vez que a execução deixa a chave de fechamento, o bloqueio é liberado e uma chamada de extração subsequente pode abrir o mesmo arquivo sem conflito.

Conclusão

[11:40 - end] Os comandos de uma linha lidam com os casos comuns: CreateFromDirectoryAsync para arquivar uma pasta inteira e ExtractToDirectoryAsync para descompactá-la. Quando você precisa de controle sobre quais arquivos entram no arquivo, a abordagem manual FileStream e ZipArchive oferece filtragem, renomeação e reescrita de caminhos no nível de entrada.

Para recapitular: adicione using System.IO.Compression, chame await ZipFile.CreateFromDirectoryAsync ou await ZipFile.ExtractToDirectoryAsync para casos diretos, e busque o caminho manual ZipArchive quando você precisar filtrar ou renomear entradas. Escopo seus blocos using com chaves se o stream precisar ser liberado antes que o código subsequente seja executado. Estas adições tornam os padrões async/await disponíveis durante todo o fluxo de trabalho zip em .NET 10.

Assista ao vídeo completo no canal do Tim Corey no YouTube para acompanhar a programação ao vivo.

Hero Worlddot related to Async Zip no .NET 10: Criação ou Extração de Uma Linha
Hero Affiliate related to Async Zip no .NET 10: Criação ou Extração de Uma Linha

Ganhe mais compartilhando o que você ama.

Você cria conteúdo para desenvolvedores que trabalham com .NET, C#, Java, Python ou Node.js? Transforme sua expertise em renda extra!

Equipe de suporte de ferro

Estamos online 24 horas por dia, 5 dias por semana.
Bater papo
E-mail
Liga para mim