跳至页脚内容
Iron Academy Logo
学习 C#
学习 C#

其他类别

.NET 10中的异步Zip:一行创建或提取

Tim Corey
~12m

在C#中通过System.IO.Compression进行Zip文件操作一直是可行的,但每次调用都是同步的,这意味着线程会被阻塞,直到归档完全写入或读取完成。 .NET 10通过一组异步重载改变了这一点,允许您创建、提取和填充Zip文件而不占用调用线程。

本演练展示了基于Tim Corey最近指南中的.NET 10中新的异步zip重载。我们将探讨三种逐步详述的方法:一行代码构建归档、单次调用解压缩,以及手动方法实现完全控制,同时考察具范围的using陷阱。

设置:路径和源文件夹

[0:28 - 1:55] 设置开始于针对.NET 10的控制台应用程序和一个使用指令:

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

三个字符串变量定义了演示中使用的路径:

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

逐字字符串前缀(@)避免了反斜杠的加倍需要。 destinationZipFile是将要创建的归档的完整路径。 destinationDirectory是提取后内容的着陆路径。

一个实用的警告:不要将sourceDirectory内的路径。 将Zip文件写入正在压缩的文件夹中会导致打断过程的递归读取循环。

测试文件夹在根目录下包含两个文件,子文件夹内包含第三个文件,这在探索includeBaseDirectory选项和手动方法中的相对路径处理时很重要。

一行代码创建存档

[2:35 - 4:20] 异步创建调用是同步的ZipFile.CreateFromDirectory的直接替代:

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

CompressionLevel.Fastest则相反。 对于大多数开发场景,这种差异可以忽略不计,但对于同时处理多个存档的Web服务器,这种取舍是值得评估的。

false(默认),zip会直接打开到文件。 传递test文件夹,实际文件在其中。 大多数使用场景受益于将其设置为false

一行代码提取存档

[4:45 - 5:55] 提取遵循相同的模式:

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

如果false,如果存档中的任何文件已存在于目标路径上,则会抛出异常。 将其设置为true即可静默替换已存在的文件。 运行两次提取可以说明这一点:第一次通过并创建文件夹,而第二次会抛出异常,除非指定了overwriteFiles: true

选择性压缩:逐个添加文件

[6:15 - 9:50] 一行代码方法压缩整个目录而不进行过滤。 当您需要只包含特定文件时,可以使用FileStreamZipArchive手动构建归档:

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

这里的几个参数值得理解。 FileMode.Create会覆盖该路径上已存在的任何文件。 FileMode.CreateNew则会在文件已存在的情况下抛出异常。 FileShare.None在归档写入期间独占锁定文件,防止其他进程在操作中途读取或写入。

在FileStream上的useAsync: true在操作系统级别启用异步I/O。 请注意,如果未实际使用异步调用就设置此参数,可能会显著降低处理速度,有时甚至慢至十倍。 由于条目写入循环使用useAsync: true。 在同步代码路径上,将其保留为默认的false

在ZipArchive上的leaveOpen: false告诉它在处置时关闭并刷新底层流。 entryNameEncoding: null保持默认编码,文档建议除非您有特定原因,否则不要更改。

档案准备好后,提取源内容并写入每个条目。

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);
}

使用"</em>.txt"将归档限制为文本文件。

sourceDirectory的部分。 这就是在zip中作为条目名称存储的内容,忠实地再现了子文件夹层次结构。 如果您传递Path.GetFileName(filePath),则每个条目不论其原始位置如何都会在zip根目录着陆。 这样会产生一个扁平的档案,但如果两个条目在不同子文件夹中共享一个文件名,就有可能发生名称冲突。

作用范围内使用的陷阱

[9:50 - 11:30] 如果您尝试在手动zip块之后添加提取调用,则会遇到文件锁定异常:The process cannot access the file because it is being used by another process。 这是因为在zipStream上的using语句采用文件范围的语法,这意味着在文件末尾而不是在zip块的花括号处关闭流。 提取尝试运行时,锁仍然被持有。

转换为大括号形式解决了这个问题。

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

将zip工作包装在大括号中并移除尾随分号,会将流的生命周期限制在该明确的作用域内。 一旦执行离开闭合大括号,锁就被释放,后续的提取调用可以不冲突地打开相同的文件。

结论

[11:40 - end] 单行代码处理常见情况:ExtractToDirectoryAsync用于解压缩它。 当您需要控制哪些文件进入归档时,手动ZipArchive方法可以让您在条目级别进行筛选、重命名和路径重写。

总结:添加ZipArchive路径。 如果流必须在后续代码运行之前释放,请使用大括号来作用域您的using块。 这些增改为.NET 10的完整zip工作流程提供了async/await模式。

观看完整的视频,在Tim Corey的YouTube频道上了解实时编码过程。

Hero Worlddot related to .NET 10中的异步Zip:一行创建或提取
Hero Affiliate related to .NET 10中的异步Zip:一行创建或提取

分享您的所爱,赚取更多收入

您为使用 .NET、C#、Java、Python 或 Node.js 的开发人员创建内容吗?将您的专业知识转化为额外收入!

钢铁支援团队

我们每周 5 天,每天 24 小时在线。
聊天
电子邮件
打电话给我