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

其他类别

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

[[academy-video-youtube({"vid": "YgQ3ta6455A", "start_time": "0", "title": ".NET 10 中的一行代码创建或提取压缩", "creator": "Tim Corey", "length": "~12m"})]]

通过System.IO.Compression,C#中的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 小时在线。
聊天
电子邮件
打电话给我