ZipArchive不支持进度监控,必须手动控制字节流:用entry.Open()获取输入流,File.Create()获取输出流,自定义带回调的CopyTo逻辑,以entry.Length为单文件基准、所有entry.Length之和为总进度分母。

用 ZipArchive 读取压缩包时根本拿不到进度
因为 ZipArchive(来自 System.IO.Compression)本身不暴露解压过程中的字节流位置或已处理项数,它只提供“打开→遍历 ZipArchiveEntry→逐个 Open() 写出”的抽象。你调用 entry.ExtractToFile() 的那一瞬间,内部就全量读完了,没留钩子给你插进度。
所以别想着靠监听 ZipArchive 自身来实现进度条——它不支持,硬套只会卡在“为什么 Position 始终是 0”上。
- 真正能拿到进度的,只有你自己控制的字节流:从
entry.Open()得到的Stream,再手动读、手动写、手动累加 - 必须放弃
ExtractToFile()和ExtractToDirectory()这类“一键解压”方法 - 如果压缩包里有 100 个文件,你得自己维护“当前第几个 + 当前文件已写多少字节”,不能只看总大小
手动复制流 + 实时回调才是唯一靠谱路径
核心思路就是:用 entry.Open() 拿到输入流,用 File.Create() 拿到输出流,自己写一个带回调的 CopyTo 变体。这样每 copy 一次 buffer,就能算一次进度。
示例关键片段:
var entryStream = entry.Open();
using var fileStream = File.Create(filePath);
var buffer = new byte[8192];
long totalBytesRead = 0;
int bytesRead;
while ((bytesRead = entryStream.Read(buffer, 0, buffer.Length)) > 0) {
fileStream.Write(buffer, 0, bytesRead);
totalBytesRead += bytesRead;
// 回调更新 UI:当前 entry 总大小是 entry.Length,已写 totalBytesRead
onProgress(entry.FullName, totalBytesRead, entry.Length);
}-
entry.Length是压缩包内该文件的原始大小(未压缩),不是压缩后大小,这才是用户关心的“进度 100%”基准 - 别用
entry.CompressedLength做分母——用户看到“解压进度 120%”会懵 - buffer 大小选 4KB–64KB 都行,太小频繁回调拖慢,太大内存占用高,8KB 是平衡点
总进度怎么算?别直接除以压缩包文件大小
压缩包本身的字节大小(比如 new FileInfo(zipPath).Length)和所有 entry.Length 之和完全不是一回事。前者是压缩后体积,后者是解压后总大小。用户想看的是“目标文件写完没”,所以总进度分母必须是所有 entry.Length 的和。
- 提前遍历一遍
zipArchive.Entries,累加所有entry.Length,存成totalUncompressedSize - 每次回调时,把当前
entry的已写量加到全局completedBytes,再除以totalUncompressedSize - 注意:空目录没有
entry,但ExtractToDirectory会自动建;你手动解压就得自己检查并创建目录,否则File.Create()会抛DirectoryNotFoundException
Win10+ 上 ZipFile.ExtractToDirectory 为啥不报错却卡住?
这个静态方法底层其实也用了 ZipArchive,但它内部是同步阻塞式执行,且不抛出 IO 异常(比如磁盘满、权限不足),而是静默失败或卡在某处。你在 UI 线程直接调它,界面就假死了,还找不到错误在哪。
- 永远不要在主线程(尤其是 WinForms/WPF UI 线程)里直接调用任何
ZipFile.*或ZipArchive.*的同步方法 - 必须用
Task.Run(() => { /* 手动流复制逻辑 */ })包一层,把耗时操作扔后台 - 如果要取消,得传
CancellationToken到每个Read()和Write()调用里,Stream类型本身支持,但默认不启用
进度条不是加个控件就行,是得把解压这件事从“黑盒调用”拆成“可观察的字节搬运”。一旦你开始手动控制流,那些看似隐蔽的路径权限、编码问题、长文件名截断,就全浮出水面了——这才是真正踩坑的地方。








