.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] 一行代码方法压缩整个目录而不进行过滤。 当您需要仅包含特定文件时,您可以使用FileStream和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);这里的几个参数值得理解。 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模式。

