盯着“剩余1秒”已经过了30秒。以为终于要结束了,显示却又变成了“剩余2分钟”。
复制文件、安装应用、导出视频时,进度条很有用,但有时它似乎和时钟不在同一个世界里运行。
事实上,进度条不是时钟。进度百分比表示“已经完成多少”,剩余时间预测“接下来大概要多久”,完成状态则表示“必要的处理是否成功”。 把这三件事分开,就能换个角度理解“明明99%了,怎么还没结束”。
本文参考Windows API与UI文档,解释一般原理,并不是对某个版本文件资源管理器内部算法的逆向分析。文中的数值示例和对比演示都是用于说明的虚拟任务,不是对电脑或网络的实测。
1. 先弄清楚:究竟是什么的100%?
假设要审阅100份文档。前99份都是简短便条,最后一份却是一大本合同,那么“已完成99份”可以完全正确,但不代表“所需时间也已经过去99%”。
进度显示也是如此:分母不同,含义就不同。
| 衡量依据 | 50%表示什么 | 仅凭这个数字无法知道什么 |
|---|---|---|
| 文件数量 | 已处理目标文件的一半 | 剩余文件的大小与处理时间 |
| 数据量 | 已处理目标字节数的一半 | 后续速度,以及传输之外的阶段 |
| 阶段权重 | 已完成预设权重的一半 | 权重是否符合本次实际耗时 |
例如,Windows的CopyFileEx使用的进度回调会提供文件总字节数与已传输字节数。这些是工作量信息,并不直接提供未来还需要多少秒。1
flowchart TB
accTitle: 进度百分比、剩余时间与完成状态的区别
accDescr: 区分根据已完成量计算的比例、基于假定速度预测的剩余时间,以及由成功结果确认的完成状态。
A["已完成量与总量"] --> B["进度百分比"]
C["剩余量与预测速度"] --> D["预计剩余时间"]
E["必要的处理成功"] --> F["整个任务完成"]
图1:比例、预测和结果,并不是同一信息的三种说法。
本文把可测量工作量的完成比例称为进度百分比,显示它的条状控件称为进度条,对还需多少秒的预测称为剩余时间估算。无法给出可靠预测,并不意味着已经测到的进度也变得未知。
图中实线表示始终成立的关系,虚线表示带条件的关系(成立条件写在详情页各关系的说明中)。关系的完整列表(共 9 条,附依据与确信度)以及主要概念的定义,汇总在知识地图详情页(日文)。数据:JSON-LD / Turtle
2. “剩余10秒”如何变成“剩余79秒”?
最简单的剩余时间估算公式如下:
剩余时间 ≈ 剩余工作量 ÷ 对未来处理速度的估计
难点不是除法,而是未来的速度还没有被观测到。因此,我们用过去的速度做预测。Microsoft的Raymond Chen在2004年解释文件复制时间估算时,也谈到了预测未来的困难。那篇文章不是当前Windows所用计算公式的规格说明。2
设想一个总量为1,000 MiB的虚拟复制任务。1 MiB等于1,048,576字节。假设任务先以80 MiB/s的恒定速度运行2.5秒,随后1秒的速度降到10 MiB/s。
| 观测时点 | 已传输 | 剩余 | 用于估算的速度 | 剩余时间 |
|---|---|---|---|---|
| 开始后2.5秒 | 200 MiB | 800 MiB | 80 MiB/s | 800 ÷ 80 = 10秒 |
| 开始后3.5秒 | 210 MiB | 790 MiB | 10 MiB/s | 790 ÷ 10 = 79秒 |
在这一秒里,任务确实向前推进了。然而,完成剩余工作的预测速度变成原来的八分之一,因此预计耗时反而增加。这个示例直接采用最近一个观测区间的速度。
flowchart TB
accTitle: 工作在推进,剩余时间为何还会增加
accDescr: 如果预测速度下降的影响超过剩余工作量减少的影响,计算出的剩余时间就会增加。
A["1秒内完成10 MiB"] --> B["剩余量减少"]
C["预测速度大幅下降"] --> D["剩余时间可能增加"]
B --> D
图2:预计剩余时间增加,与任务实际倒退是两回事。
“剩余1秒”也是同样的道理。按照刚才的速度只需1秒的工作量,如果接下来的处理更慢,就无法在1秒内结束。观测间隔和秒数的取整方式也会影响显示。不过,如果数字始终不变,也不能直接认定为正常;还需要按后文的方法,区分处理阶段、界面更新和实际停滞。
3. 取平均值就能算准吗?
如果把每次短暂的速度变化都反映出来,数字就会上下跳动。反过来,如果只采用从开始到现在的平均速度,最初很快的那一段就会持续影响结果,导致预测迟迟无法适应后来的持续减速。
从观测值的组合方式就能理解这种差异。假设把过去的速度80与最新速度10各按一半权重混合,得到的预测速度是45。与只使用最新值10相比,预测会平滑一些;但如果实际速度一直维持在10,它就会在一段时间内过于乐观。平滑与及时跟上变化,是不同的目标。
flowchart TB
accTitle: 平滑速度估计时的取舍
accDescr: 提高最新观测值的权重会让估计更敏感但也更易波动,提高历史值的权重则更平滑但跟随变化较慢。
A["速度观测值"] --> B["提高最新值的权重"]
A --> C["提高历史值的权重"]
B --> D["反应敏感但容易波动"]
C --> E["显示平滑但跟随较慢"]
图3:数字看起来稳定,不等于预测准确。
这不是某个产品的实现说明,而是帮助思考预测方法的例子。本文的设计建议是:在刚开始或刚切换阶段时,不要勉强显示秒数;有了足够观测后,再以“大约1分钟”等形式给出估计。与其维持一个没有依据的“剩余1秒”,不如明确说明正在重新估算,以减少误解。
4. 已完成99个文件,数据量却只完成9.9%
这次问题不在速度,而在计数单位。假设有100个文件,前99个各为1 MiB,最后一个为901 MiB,总量是1,000 MiB。
前99个文件完成时,按数量计算是99 ÷ 100 = 99%,按数据量计算却是99 ÷ 1,000 = 9.9%。虽然只剩一个文件,仍有90.1%的数据尚未传输。两种计算都正确,只是衡量的对象不同。
flowchart TB
accTitle: 最后一个文件特别大时的进度
accDescr: 处理完99个小文件后,如果还剩一个大文件,按文件数量与按字节数计算的进度就会相差很大。
A["99个小文件"] --> B["按数量看几乎完成"]
C["最后还剩一个大文件"] --> D["仍有大量数据未传输"]
B --> E["同一任务的不同显示"]
D --> E
图4:按文件数量达到99%,并不承诺只剩1%的时间。
那么,只看字节数就够了吗?也不是。通过SMB传输大量小文件时,会反复产生创建文件和请求往返的开销。即使总字节数与一个大文件相同,耗时也未必相同。3
所以,“还剩500 MiB”可以是准确的测量结果,但这500 MiB由什么组成,仍会影响所需时间。同时显示文件数量与字节数的价值,并不在于其中一个数字有错,而在于它们能补充彼此未体现的信息。
5. “准备中”很久,有时是在寻找分母
计算百分比需要作为分母的总量。但刚发出“处理整个文件夹”的指令时,程序未必已经枚举完里面所有层级的文件。
假设程序认为共有100项,处理了80项后又发现100项。同样的80项就从80/100变成80/200。显示从80%下降到40%,但已经完成的工作并没有消失。把尚未确定的总量当作确定值展示,才造成了与读者预期的偏差。
flowchart TB
accTitle: 总量尚未确定时如何显示进度
accDescr: 查找处理对象时不显示确定的比例,等总量已知后,再结合已完成量计算百分比。
A["查找处理对象"] --> B{"总量已知了吗"}
B -->|"还没有"| C["显示阶段与已发现数量"]
B -->|"已知"| D["显示百分比"]
图5:没有分母,与进度为0%是不同的状态。
Windows的进度控件也区分确定进度和不确定进度;后者用来表示仍在处理中,而不提供确定数值。4 对于这里的情况,本文建议在枚举时显示“正在查找对象:已发现1,200项”,等总量确定后再显示比例。没有剩余秒数,本身并不能证明程序什么都没做。
6. “传输100%”与“全部结束”不是同一个边界
6.1 最后可能还有别的工作
为了说明问题,把一个应用的任务分成“传输 → 校验 → 确定最终结果”三个阶段。如果设计要求传输后校验内容,那么传输结束时,整个任务还没有完成。这只是一个应用设计示例,并不意味着所有复制或安装都遵循这个顺序。
flowchart TB
accTitle: 传输完成与整个任务完成
accDescr: 这个虚拟应用在传输后还需要校验并确定最终结果,因此不能仅凭已传输字节数判断整体成功。
A["传输"] --> B["校验"] --> C["确定最终结果"] --> D["整体成功"]
A -.-> E["传输100%只到这里"]
图6:明确说明完成的范围,就能解释为何100%后仍有其他阶段。
此时,与其把代表整个任务的进度条固定在99%,一直显示“剩余1秒”,不如把状态切换为“传输完成,正在校验”。Microsoft的桌面UI指南也要求,不要在实际操作结束之前显示整个任务已经完成。5
6.2 写入也不只有一个完成边界
Windows通常会对文件写入使用缓存。应用发出写入与数据落到存储介质,具体边界会因设置及API约定而异。6 FlushFileBuffers是把指定文件的缓冲信息发送到设备的API。7
flowchart TB
accTitle: 缓冲写入的概念图
accDescr: 使用缓冲时,应用提交的写入与存储介质侧的实际写入,不应当作同一时刻发生的事件。
A["应用发出写入"] --> B["保存在缓冲区"] --> C["写入存储介质"]
图7:这是使用缓冲时的概念图,不是某个产品的完成条件说明。
但是,不能说“卡在99%一定是在刷写缓存”。到底是在校验、刷写,还是等待别的操作,需要检查应用设计或记录。不要仅凭进度数字,就推断可以拔掉USB设备或切断电源。
7. 是任务真的停了,还是只有显示停了?
在Windows的WPF应用中,UI线程上的Dispatcher负责处理界面工作。如果长时间占用该线程,界面更新和输入响应就会延迟。实际执行的任务,与把结果显示到屏幕上的工作,需要分开考虑。8
flowchart TB
accTitle: 从实际处理到进度显示的路径
accDescr: 即使实际任务已经报告进度,如果UI一侧无法处理更新,变化也不会反映到屏幕上。
A["实际处理"] --> B["报告进度"] --> C["UI处理更新"] --> D["反映到屏幕"]
E["UI线程阻塞"] -.-> C
图8:显示停止,不一定意味着实际处理也停止。
反过来,如果界面动画与实际处理独立运行,那么实际任务处于等待状态时,圆形动画仍然可以转动。“在动就正常,不动就故障”这样的二分判断并不够。
| 观察什么 | 可以获得什么线索 | 仅凭它无法确定什么 |
|---|---|---|
| 已处理数量、数据量、阶段名称是否变化 | 已报告的工作进展 | 整个任务最终能否成功 |
| 日志中的时间、对象与错误 | 记录显示在哪一步做什么 | 没有写日志的处理是否已经停止 |
| 相关进程的CPU、磁盘与网络活动 | 当时的资源使用情况 | 是正常推进、等待,还是无效反复 |
| 其他窗口中的确认提示 | 是否在等待用户输入 | 所有可能的停滞原因 |
本文建议先记录界面与开始时间,检查是否有等待确认的提示,再比较处理数量或日志是否变化。任务管理器里的数值只作为辅助证据。考虑结束任务之前,应先确认应用的取消方式,以及未完成的输出会如何处理。
.NET的取消机制也是协作式的:发出取消请求并不会立即停止处理,处理方需要响应请求。9 因此,“正在取消”和“已取消”也应该是不同状态。仅凭进度显示,无法得出一个人人适用的“超过几分钟就可以安全强制结束”的数值。
8. 对比同一任务的三种显示方式
这个演示通过按钮切换虚拟任务的观测时点。不需要等待,也不会读取、写入或上传文件。第一个案例是“99个小文件加1个大文件”。同一时点的文件数量进度条、传输量进度条和任务整体状态会并列展示。
传输最后一个大文件的过程中,按数量计算的进度条仍停在99%,但按数据量计算的进度条会继续增长。传输量达到100%时,整体状态仍是校验中;推进到下一个观测时点,才会变成成功。由此可以看到,仅仅换一种显示方式,同一任务就可能看起来像停住了,也可能明显在推进。
flowchart TB
accTitle: 如何阅读对比演示
accDescr: 将同一虚拟任务在同一时点的观测值,分别交给文件数量、传输量和整体状态三种显示。
A["一个虚拟任务"] --> B["同一观测时点"]
B --> C["文件数量比例"]
B --> D["传输量比例"]
B --> E["任务整体状态"]
图9:演示比较的是一个任务的三种视图,而不是三个不同的任务。
另一个案例重现第2节的速度下降,显示10秒如何算成79秒。所用计算如下。这只是用于学习的简单预测,并没有实现实际应用中的重试、并行处理或分阶段预测。
function estimateSeconds(remaining, rate) {
if (!Number.isFinite(remaining) || remaining < 0) return null;
if (remaining === 0) return 0;
if (!Number.isFinite(rate) || rate <= 0) return null;
const seconds = remaining / rate;
return Number.isFinite(seconds) ? seconds : null;
}
传入remaining和rate时,必须保持单位一致,例如MiB与MiB/s。这里返回0只表示估算范围内已经没有剩余工作量,不表示应用的整个任务已经成功。剩余量为正且速度为0或未知时返回null,避免把“无法估算”误当成“剩余0秒”。
9. 开发者应追求“不误导”,而不只是“看起来精确”
对于本文讨论的设计,我会分别保存并显示当前阶段、已测得的工作量、有依据时才提供的剩余时间,以及成功、失败或取消的结果。即使把各阶段百分比合并成一根进度条,“传输80%、校验20%”也只是设计上的权重,不保证本次耗时就按这个比例分配。
flowchart TB
accTitle: 根据观测信息构建进度界面
accDescr: 将当前阶段、实际测量的数量、有依据时的剩余时间估算,以及最终结果分开显示。
A["任务提供的信息"] --> B["当前阶段"]
A --> C["已测量工作量与总量"]
A --> D["条件满足时的估算"]
A --> E["成功、失败或取消"]
图10:在界面上也要区分观测结果与预测。
例如,“正在校验:400 / 1,000项,正在估算剩余时间”即使没有秒数也很有用。如果显示更新时间,“进度最后增长的时间”与“界面最后成功通信的时间”也不是一回事。不要只刷新后者,却让用户误以为实际任务一直在正常推进。
无障碍设计也是同样的思路。Web中的progress元素可以通过省略值来表示不确定进度。10 对于自定义的ARIA进度显示,值未知时应省略aria-valuenow,并提供能够说明“是什么的进度”的无障碍名称。11 不仅视觉显示,屏幕阅读器读出的信息也应该如实反映当前已知的范围。
10. 常见问题
一直显示剩余1秒,是发生故障了吗?
仅凭显示无法判断。要区分估算失准、其他阶段、界面更新延迟和任务实际停滞。“经常这样,所以不用管”和“超过1秒了,所以一定坏了”都过于武断。
99%是否意味着只剩总时间的1%?
不是。不论比例的单位是文件还是字节,每个单位所需的时间都未必固定。不能用已过去的总时间乘以1%,来求出剩余时间。
圆形动画还在转,就代表正常吗?
表示正在工作的动画,与任务实际推进的证据是两回事。应结合已完成量、阶段和日志来判断。单凭动画不能保证任务能正常完成。
不知道剩余时间,就不能显示进度吗?
知道总量与已完成量,就可以显示比例。预测不可靠时只省略秒数。如果连总量都未知,就采用不确定进度显示,并附上阶段名称和已处理数量。
11. 总结:剩余时间是预测,完成状态是结果
“剩余1秒”持续很久,并不是因为电脑不会计算1秒,而是因为它要根据已经观察到的工作量,估计还没发生的处理时间。再加上计数单位、目标查找、最后的阶段与界面更新延迟,情况就更加复杂。
flowchart TB
accTitle: 如何解读迟迟不结束的进度显示
accDescr: 依次确认比例在衡量什么、预测速度是否变化、是否还有其他阶段,以及停住的是显示还是实际处理。
A["衡量什么的比例"] --> B["预测速度是否改变"] --> C["是否还剩其他阶段"] --> D["显示停滞还是处理停滞"]
图11:把对数字的不满,拆成可以核实的问题。
用户应关注阶段和变化,而不只盯着数字;开发者不要把工作量、预测和结果混在一起。相比一直猜准“还剩1秒”,更重要的是说明现在正在发生什么,以及哪些事情仍然未知。这才是好的进度显示应该做的事。
参考资料
-
Microsoft Learn, LPPROGRESS_ROUTINE callback function. 复制进度回调所提供字节数的含义。 ↩
-
Microsoft, The Old New Thing, Why does the copy dialog give such horrible estimates?. 2004年的解释,用来说明预测未来速度的困难,并非当前Windows内部实现的规格说明。 ↩
-
Microsoft Learn, Slow SMB files transfer speed. 小文件传输反复产生的文件创建和通信开销。 ↩
-
Microsoft Learn, Progress controls. 确定与不确定进度的控件。 ↩
-
Microsoft Learn, Progress Bars. 桌面应用进度显示的设计指南。 ↩
-
Microsoft Learn, File Caching. 文件缓存与写入行为。 ↩
-
Microsoft Learn, FlushFileBuffers function. 把文件缓冲信息发送到设备的API。 ↩
-
Microsoft Learn, Threading model. WPF的Dispatcher与UI线程响应能力。 ↩
-
Microsoft Learn, Cancellation in Managed Threads. .NET的协作式取消。 ↩
-
WHATWG, The progress element. HTML进度元素与不确定状态。 ↩
-
W3C, WAI-ARIA 1.2: progressbar. 无障碍进度信息的名称与数值规则。 ↩
相关文章
共享相同标签的最新文章。可以围绕相近的主题进一步加深理解。
同样是 1GB,为什么复制照片文件夹比复制一部视频还慢?
图解 Windows 中容量相同、复制耗时却不同的原因。梳理文件数量、SSD 与 NAS 的等待时间、打包成 ZIP 的效果、把创建·传输·解压都算进去的对比步骤,以及 robocopy 的适用场景。
Windows 的“硬件加速 GPU 计划”是什么?打开就会变快吗?
面向普通用户图解 Windows 的硬件加速 GPU 计划(HAGS):它改变了什么、开与关如何判断、设置项不显示的原因、与帧生成的关系,以及安全的对比步骤。
WPR/WPA 实务 ── 从系统整体调查「整台 PC 变慢」
任务管理器追不到的「整台 PC 变慢」「开机很慢」,可用 WPR/WPA 采集整个 OS 的 ETW 追踪再读。本文说明 wpr.exe 采集步骤,以及在 WPA 里怎么读 CPU、等待与磁盘 I/O。
WPF 高 DPI 支持──「不是应该对 DPI 很强吗」却仍模糊、渗色的原因与对策
WPF 以 DIP(1/96 英寸)进行布局,从一开始就是 System DPI Aware,但移动到 DPI 不同的显示器时整体会渗色,位图与细线也会模糊。本文从实务角度整理原因判断、.NET Framework 4.6.2/.NET 上的 Per-Monitor DPI...
WinForms 的高DPI支持 - 4K显示器下模糊、错位的原因与实用对策
本文从 DPI 虚拟化与 DPI 感知模式(System Aware / Per-Monitor V2)的角度,整理 WinForms 应用在4K显示器、150%缩放环境下显示模糊、布局错乱的原因,并说明 .NET 与 .NET Framework 各自的配置方法、Auto...
相关主题
与本文相近的主题页面。以本文为起点,可以进一步了解相关服务和其他文章。
Windows 技术主题
汇整 KomuraSoft LLC 关于 Windows 开发、故障调查与既有资产活用文章的主题中心。
UI 线程 & 计时器
整理 WPF / WinForms UI 线程、异步流程、Dispatcher 使用、计时器判断的主题页面。
常见问题
汇总了咨询这一主题时常见的问题。
- 一直显示剩余1秒,是发生故障了吗?
- 仅凭显示无法判断。需要区分速度预测失准、最后的处理阶段、界面没有更新和任务真正停滞。检查处理数量与日志是否变化,以及是否有等待确认的窗口,不要仅凭剩余1秒这个数字就强制结束应用。
- 进度达到99%,是否意味着只剩总时间的1%?
- 不是。99%的含义取决于它衡量的是数量、数据量还是阶段权重,而且每个单位的处理时间未必相同。进度百分比是工作量的比例,不是剩余时间本身。
- 圆形动画还在转,就能证明任务正常吗?
- 不能。如果界面动画与实际处理独立运行,任务处于等待状态时动画也可以继续转动。应把动画与处理数量、阶段变化、日志等实际进展的证据区分开。
- 无法准确估算剩余时间,就不能显示进度条吗?
- 可以显示。只要知道总量和已完成量,就能显示比例;速度不稳定时可以只省略剩余时间。如果连总量也未知,应显示阶段或已处理数量,而不是编造百分比。