跳至页脚内容
Iron Academy Logo
C# 和 .NET 中的新内容

异步C#: 与Tim Corey一起深入探索异步Zip

Tim Corey
24m 49s

文件压缩和提取对于许多C#开发人员来说是至关重要的任务,无论您是正在开发桌面应用、Web服务器,还是仅需要存档数据。 多年来,.NET开发人员可以同步创建、更新和提取zip文件,阻塞UI线程或延迟其他操作。 正如Tim Corey在其视频"Async Zip in .NET 10 – One Line Create or Extract And More"中解释的那样,.NET 10为zip文件引入了异步操作,允许开发人员编写执行I/O绑定操作的高效代码而不冻结主程序。

C#中的异步编程主要基于Task和Task对象,Task类作为建模进行中的工作的核心组件,并支持并发执行。 任务异步编程(TAP)模型在典型的异步编码之上提供了一个抽象层,使处理异步操作和异常变得更容易。 System.Threading.Tasks.Task类和相关类型用于在C#中实现基于任务的异步编程模型。

本文将遵循Tim的解释,展示如何在C#中执行异步操作以创建、提取和有选择性地压缩文件,同时使用异步/等待模式、Task对象和适当的异常处理。

异步编程和.NET 10中的异步Zip简介

Tim首先指出了C#长期以来能够创建和提取zip文件的能力。 但是,随着.NET 10的引入,开发人员现在可以使用异步代码来处理这些任务而不阻塞调用线程。 使用异步方法和await关键字,创建或提取大型zip文件等操作可以在后台线程上运行,从而使主UI线程保持响应。

async关键字只是一个装饰器,告诉C#编译器方法至少包含await关键字一次。 await关键字只能在异步方法中使用,且必须跟随一个Task或Task对象。

正如Tim解释的那样,这在执行多个任务的应用程序中尤其重要,例如网络调用、数据库查询或文件I/O,等待任务能让CPU绑定的任务继续进行而不阻塞调用线程。 使用.Result或.Wait()阻塞异步代码会将异步代码变回同步代码,这可能导致死锁。

异步方法返回Task或Task对象,而返回任务使得适当的任务组合和并发成为可能。 没有await的情况下方法是同步执行的。 通过从异步方法返回Task对象,您可以协调多个正在进行的操作并更有效地处理异常。

项目设置

Tim通过打开Visual Studio 2026并创建一个名为ZipFilesApp的控制台应用程序开始。他注意到连静态异步Task Main方法都可以用来在程序的入口点执行异步任务,避免阻塞主线程。 在适应现有代码时,您通常可以用Tasks包装同步代码或实现异步变体以引入异步行为而不需要重写整个代码库。

然后,他添加了一个必要的using指令:

using System.IO.Compression;

这使得访问异步方法如CreateFromDirectoryAsync和ExtractToDirectoryAsync成为可能。 创建调用方法时,使用一致的命名惯例非常重要,例如为异步方法加'Async'后缀,以清楚地区分它们与同步方法。 不一致的方法命名可能导致对方法是同步还是异步的困惑。

接下来,Tim准备了三个字符串变量以定义zip操作所需的路径:

  1. 源目录 – 要压缩的文件夹。

  2. 目标zip文件 – 保存zip文件的完整路径。

  3. 目标目录 – 提取后文件存储的文件夹。

Tim提到在字符串字面量中使用@符号以避免转义反斜杠,这是在Windows路径中编写同步代码时常见的问题。 在整篇文章中,代码示例和代码片段用于展示如何在C#中实现异步编程。

使用异步方法创建Zip文件

在3:47时,Tim演示了使用静态异步Task方法的一行异步操作:

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

在这里,Tim解释了几项重要内容:

  • async关键字标记此异步方法,使得可以使用await。

  • await暂停此异步方法的执行直到任务完成,允许其他任务或主UI线程继续运行。 这展示了await的工作原理以及暂停执行如何确保异步操作中的非阻塞行为,例如文件I/O。

  • CompressionLevel.SmallestSize确保高效压缩。

  • includeBaseDirectory参数控制是否在zip中包含根文件夹。

该示例展示了异步工作流程在C#中如何通过async和await关键字进行管理,使代码更易维护并简化异常处理。 await关键字只能在异步方法中使用,例如公共异步Task方法。 遇到await时,C#编译器会生成状态机以管理执行流,暂停执行,启动异步任务并在完成后恢复。 如果async方法返回值,该结果可以保存到变量中,如int result,以便后续使用。 通过使用async/await,开发人员无需手动创建后台线程或线程池任务,因为编译器会自动生成必要的状态机和支持代码。

异步提取Zip文件与Await任务

Tim转到使用另一个异步方法提取zip文件:

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

他指出overwriteFiles参数在文件夹已存在时处理异常。 默认情况下,如果文件已存在,提取将失败,但将其设置为true允许异步操作替换现有文件。

当处理异步代码时,强大的错误处理至关重要。 Tim强调使用try-catch块来在异步方法中处理异常的重要性。 在异步代码中不正确处理异常可能导致静默失败或意外崩溃,因为抛出的异常在异步任务中可能不会立即可见。 请始终处理异常以确保可靠的应用程序行为。

Tim指出这种异步代码适用于多个任务,这意味着不会阻塞同时发生的其他请求或网络调用。 使用await关键字,调用线程在任务完成之前被释放,创建一个响应的程序流。

另外,在定义异步方法时,async void只应用于事件处理程序,例如GUI应用程序中的按钮点击。 在这些情况下,方法签名通常包括(object sender, EventArgs e),其中object sender识别事件的来源。 在事件处理程序之外使用async void可能导致不可预测的行为和难以诊断的错误,因此在大多数异步方法中应优先考虑使用async Task。

有选择性地添加文件到Zip

有时候,开发人员需要比仅仅压缩整个文件夹更多的控制。 Tim演示通过创建一个FileStream执行有选择性的异步操作:

await using FileStream zipStream = new FileStream(
    destinationZipFile, 
    FileMode.Create, 
    FileAccess.Write, 
    FileShare.None, 
    bufferSize: 4096, 
    useAsync: true
);

当异步添加文件时,每个文件添加表示一个特定任务。 通过同时启动所有文件添加任务然后使用Task.WhenAll等待它们完成,您可以显著提高性能。 以这种方式管理运行任务对于高并发场景至关重要,例如当服务器需要同时处理多个请求。 这种方法通常在与远程服务器交互时使用,使应用程序在等待数据时保持响应。

Tim解释每一个细节:

  • FileMode.Create – 确保创建或覆盖新的zip文件。

  • FileAccess.Write – 允许写入文件。

  • FileShare.None – 在文件被写入时阻止其他任务访问该文件。

  • useAsync: true – 启用异步文件写入,这是核心的异步操作。

他指出在这种情况下异步编程避免了任务完成时阻塞主线程,这在处理多个任务的Web服务器上尤其有用。 高并发服务器利用异步编程高效地处理多个同时的客户端请求,而不必为每个连接生成一个新线程。 然而,顺序编写异步代码可能会使其更难阅读和调试,因为异步任务可能会无序完成。

创建Zip归档并添加文件

接下来,Tim通过异步创建ZipArchive:

using ZipArchive archive = await ZipArchive.CreateAsync(
    zipStream, 
    ZipArchiveMode.Create, 
    leaveOpen: false, 
    entryNameEncoding: null
);
  • await表达式确保在任务完成之前暂停执行,使得添加文件操作安全进行。 Async和await简化异步操作的管理,使代码相比传统回调方式更易于阅读和维护。

  • 需要注意的是,根据同步上下文相同代码的行为可能会有所不同。 例如,在WPF或WinForms应用程序中,存在同步上下文会导致在await调用后继续在主UI线程上运行,而在控制台应用程序中则没有这样的上下文。 这可能影响异步代码的执行方式。

  • 在编写库代码时,考虑在await调用后使用ConfigureAwait(false)以避免不必要的上下文切换并提高性能。

  • 使用Path.GetRelativePath,Tim解释如何保持zip中的文件夹结构,当处理包含多个文件的异步工作流程时这至关重要:
foreach (string filePath in Directory.GetFiles(sourceDirectory, "*", SearchOption.AllDirectories))
{
    string relativePath = Path.GetRelativePath(sourceDirectory, filePath);
    await archive.CreateEntryFromFileAsync(filePath, relativePath).ConfigureAwait(false);
}

这展示了实践中的异步代码,其中每个文件添加是一个异步调用,await关键字确保正确的排序同时仍执行非阻塞I/O。 异步代码允许当前线程保持未阻塞,提高响应性并允许线程在等待I/O操作完成时执行其他工作。

处理文件访问和异常处理

Tim突出了一个常见的异常处理场景:在原始FileStream任务完成之前尝试提取zip。 使用文件作用域async void方法或async Task方法而没有适当的作用域using语句可能会导致:

由于该文件正在被另一个进程使用,无法访问该文件。

解决方案是将异步操作包裹在传统作用域using块中以释放资源在提取之前:

await using (FileStream zipStream = new FileStream(...))
{
    using ZipArchive archive = await ZipArchive.CreateAsync(zipStream, ...);
    // 异步添加文件
}
// 现在可以安全提取

这确保任务对象完成并释放同步上下文,避免文件锁定错误。

结论:.NET 10中高效的异步Zip

Tim总结说,.NET 10中的异步zip操作允许开发者高效地执行异步操作,避免阻塞主线程,并与其他任务如网络调用或数据库查询无缝集成。

使用静态异步Task方法、异步修饰符和await表达式,开发者可以:

  • 在不冻结UI线程的情况下压缩或提取zip文件。

  • 在不需手动线程池管理的情况下安全处理多个线程。

  • 执行选择性压缩,同时保留文件夹结构。

  • 确保正确的异常处理和资源清理。

Tim的例子展示了在文件操作中使用async/await不仅方便,它是C#中现代异步编程的一个重要组成部分。

通过观看他的 视频,开发者可以使用异步方法、等待调用和异步工作流来无缝地处理简单和复杂的文件压缩任务。

Hero Worlddot related to 异步C#: 与Tim Corey一起深入探索异步Zip
Hero Affiliate related to 异步C#: 与Tim Corey一起深入探索异步Zip

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

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

钢铁支援团队

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