为什么“剩余1秒”迟迟不结束?── 进度条与剩余时间的工作原理

· 更新日期: · · Windows, 进度条, 剩余时间, UI, 性能

盯着“剩余1秒”已经过了30秒。以为终于要结束了,显示却又变成了“剩余2分钟”。

复制文件、安装应用、导出视频时,进度条很有用,但有时它似乎和时钟不在同一个世界里运行。

事实上,进度条不是时钟。进度百分比表示“已经完成多少”,剩余时间预测“接下来大概要多久”,完成状态则表示“必要的处理是否成功”。 把这三件事分开,就能换个角度理解“明明99%了,怎么还没结束”。

本文参考Windows API与UI文档,解释一般原理,并不是对某个版本文件资源管理器内部算法的逆向分析。文中的数值示例和对比演示都是用于说明的虚拟任务,不是对电脑或网络的实测。

1. 先弄清楚:究竟是什么的100%?

假设要审阅100份文档。前99份都是简短便条,最后一份却是一大本合同,那么“已完成99份”可以完全正确,但不代表“所需时间也已经过去99%”。

进度显示也是如此:分母不同,含义就不同。

衡量依据 50%表示什么 仅凭这个数字无法知道什么
文件数量 已处理目标文件的一半 剩余文件的大小与处理时间
数据量 已处理目标字节数的一半 后续速度,以及传输之外的阶段
阶段权重 已完成预设权重的一半 权重是否符合本次实际耗时

例如,Windows的CopyFileEx使用的进度回调会提供文件总字节数与已传输字节数。这些是工作量信息,并不直接提供未来还需要多少秒。1

进度百分比、剩余时间与完成状态的区别区分根据已完成量计算的比例、基于假定速度预测的剩余时间,以及由成功结果确认的完成状态。已完成量与总量进度百分比剩余量与预测速度预计剩余时间必要的处理成功整个任务完成

图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秒

在这一秒里,任务确实向前推进了。然而,完成剩余工作的预测速度变成原来的八分之一,因此预计耗时反而增加。这个示例直接采用最近一个观测区间的速度。

工作在推进,剩余时间为何还会增加如果预测速度下降的影响超过剩余工作量减少的影响,计算出的剩余时间就会增加。1秒内完成10 MiB剩余量减少预测速度大幅下降剩余时间可能增加

图2:预计剩余时间增加,与任务实际倒退是两回事。

“剩余1秒”也是同样的道理。按照刚才的速度只需1秒的工作量,如果接下来的处理更慢,就无法在1秒内结束。观测间隔和秒数的取整方式也会影响显示。不过,如果数字始终不变,也不能直接认定为正常;还需要按后文的方法,区分处理阶段、界面更新和实际停滞。

3. 取平均值就能算准吗?

如果把每次短暂的速度变化都反映出来,数字就会上下跳动。反过来,如果只采用从开始到现在的平均速度,最初很快的那一段就会持续影响结果,导致预测迟迟无法适应后来的持续减速。

从观测值的组合方式就能理解这种差异。假设把过去的速度80与最新速度10各按一半权重混合,得到的预测速度是45。与只使用最新值10相比,预测会平滑一些;但如果实际速度一直维持在10,它就会在一段时间内过于乐观。平滑与及时跟上变化,是不同的目标。

平滑速度估计时的取舍提高最新观测值的权重会让估计更敏感但也更易波动,提高历史值的权重则更平滑但跟随变化较慢。速度观测值提高最新值的权重提高历史值的权重反应敏感但容易波动显示平滑但跟随较慢

图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%的数据尚未传输。两种计算都正确,只是衡量的对象不同。

最后一个文件特别大时的进度处理完99个小文件后,如果还剩一个大文件,按文件数量与按字节数计算的进度就会相差很大。99个小文件按数量看几乎完成最后还剩一个大文件仍有大量数据未传输同一任务的不同显示

图4:按文件数量达到99%,并不承诺只剩1%的时间。

那么,只看字节数就够了吗?也不是。通过SMB传输大量小文件时,会反复产生创建文件和请求往返的开销。即使总字节数与一个大文件相同,耗时也未必相同。3

所以,“还剩500 MiB”可以是准确的测量结果,但这500 MiB由什么组成,仍会影响所需时间。同时显示文件数量与字节数的价值,并不在于其中一个数字有错,而在于它们能补充彼此未体现的信息。

5. “准备中”很久,有时是在寻找分母

计算百分比需要作为分母的总量。但刚发出“处理整个文件夹”的指令时,程序未必已经枚举完里面所有层级的文件。

假设程序认为共有100项,处理了80项后又发现100项。同样的80项就从80/100变成80/200。显示从80%下降到40%,但已经完成的工作并没有消失。把尚未确定的总量当作确定值展示,才造成了与读者预期的偏差。

总量尚未确定时如何显示进度查找处理对象时不显示确定的比例,等总量已知后,再结合已完成量计算百分比。还没有已知查找处理对象总量已知了吗显示阶段与已发现数量显示百分比

图5:没有分母,与进度为0%是不同的状态。

Windows的进度控件也区分确定进度和不确定进度;后者用来表示仍在处理中,而不提供确定数值。4 对于这里的情况,本文建议在枚举时显示“正在查找对象:已发现1,200项”,等总量确定后再显示比例。没有剩余秒数,本身并不能证明程序什么都没做。

6. “传输100%”与“全部结束”不是同一个边界

6.1 最后可能还有别的工作

为了说明问题,把一个应用的任务分成“传输 → 校验 → 确定最终结果”三个阶段。如果设计要求传输后校验内容,那么传输结束时,整个任务还没有完成。这只是一个应用设计示例,并不意味着所有复制或安装都遵循这个顺序。

传输完成与整个任务完成这个虚拟应用在传输后还需要校验并确定最终结果,因此不能仅凭已传输字节数判断整体成功。传输校验确定最终结果整体成功传输100%只到这里

图6:明确说明完成的范围,就能解释为何100%后仍有其他阶段。

此时,与其把代表整个任务的进度条固定在99%,一直显示“剩余1秒”,不如把状态切换为“传输完成,正在校验”。Microsoft的桌面UI指南也要求,不要在实际操作结束之前显示整个任务已经完成。5

6.2 写入也不只有一个完成边界

Windows通常会对文件写入使用缓存。应用发出写入与数据落到存储介质,具体边界会因设置及API约定而异。6 FlushFileBuffers是把指定文件的缓冲信息发送到设备的API。7

缓冲写入的概念图使用缓冲时,应用提交的写入与存储介质侧的实际写入,不应当作同一时刻发生的事件。应用发出写入保存在缓冲区写入存储介质

图7:这是使用缓冲时的概念图,不是某个产品的完成条件说明。

但是,不能说“卡在99%一定是在刷写缓存”。到底是在校验、刷写,还是等待别的操作,需要检查应用设计或记录。不要仅凭进度数字,就推断可以拔掉USB设备或切断电源。

7. 是任务真的停了,还是只有显示停了?

在Windows的WPF应用中,UI线程上的Dispatcher负责处理界面工作。如果长时间占用该线程,界面更新和输入响应就会延迟。实际执行的任务,与把结果显示到屏幕上的工作,需要分开考虑。8

从实际处理到进度显示的路径即使实际任务已经报告进度,如果UI一侧无法处理更新,变化也不会反映到屏幕上。实际处理报告进度UI处理更新反映到屏幕UI线程阻塞

图8:显示停止,不一定意味着实际处理也停止。

反过来,如果界面动画与实际处理独立运行,那么实际任务处于等待状态时,圆形动画仍然可以转动。“在动就正常,不动就故障”这样的二分判断并不够。

观察什么 可以获得什么线索 仅凭它无法确定什么
已处理数量、数据量、阶段名称是否变化 已报告的工作进展 整个任务最终能否成功
日志中的时间、对象与错误 记录显示在哪一步做什么 没有写日志的处理是否已经停止
相关进程的CPU、磁盘与网络活动 当时的资源使用情况 是正常推进、等待,还是无效反复
其他窗口中的确认提示 是否在等待用户输入 所有可能的停滞原因

本文建议先记录界面与开始时间,检查是否有等待确认的提示,再比较处理数量或日志是否变化。任务管理器里的数值只作为辅助证据。考虑结束任务之前,应先确认应用的取消方式,以及未完成的输出会如何处理。

.NET的取消机制也是协作式的:发出取消请求并不会立即停止处理,处理方需要响应请求。9 因此,“正在取消”和“已取消”也应该是不同状态。仅凭进度显示,无法得出一个人人适用的“超过几分钟就可以安全强制结束”的数值。

8. 对比同一任务的三种显示方式

打开进度条对比演示

这个演示通过按钮切换虚拟任务的观测时点。不需要等待,也不会读取、写入或上传文件。第一个案例是“99个小文件加1个大文件”。同一时点的文件数量进度条、传输量进度条和任务整体状态会并列展示。

传输最后一个大文件的过程中,按数量计算的进度条仍停在99%,但按数据量计算的进度条会继续增长。传输量达到100%时,整体状态仍是校验中;推进到下一个观测时点,才会变成成功。由此可以看到,仅仅换一种显示方式,同一任务就可能看起来像停住了,也可能明显在推进。

如何阅读对比演示将同一虚拟任务在同一时点的观测值,分别交给文件数量、传输量和整体状态三种显示。一个虚拟任务同一观测时点文件数量比例传输量比例任务整体状态

图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%”也只是设计上的权重,不保证本次耗时就按这个比例分配。

根据观测信息构建进度界面将当前阶段、实际测量的数量、有依据时的剩余时间估算,以及最终结果分开显示。任务提供的信息当前阶段已测量工作量与总量条件满足时的估算成功、失败或取消

图10:在界面上也要区分观测结果与预测。

例如,“正在校验:400 / 1,000项,正在估算剩余时间”即使没有秒数也很有用。如果显示更新时间,“进度最后增长的时间”与“界面最后成功通信的时间”也不是一回事。不要只刷新后者,却让用户误以为实际任务一直在正常推进。

无障碍设计也是同样的思路。Web中的progress元素可以通过省略值来表示不确定进度。10 对于自定义的ARIA进度显示,值未知时应省略aria-valuenow,并提供能够说明“是什么的进度”的无障碍名称。11 不仅视觉显示,屏幕阅读器读出的信息也应该如实反映当前已知的范围。

10. 常见问题

一直显示剩余1秒,是发生故障了吗?

仅凭显示无法判断。要区分估算失准、其他阶段、界面更新延迟和任务实际停滞。“经常这样,所以不用管”和“超过1秒了,所以一定坏了”都过于武断。

99%是否意味着只剩总时间的1%?

不是。不论比例的单位是文件还是字节,每个单位所需的时间都未必固定。不能用已过去的总时间乘以1%,来求出剩余时间。

圆形动画还在转,就代表正常吗?

表示正在工作的动画,与任务实际推进的证据是两回事。应结合已完成量、阶段和日志来判断。单凭动画不能保证任务能正常完成。

不知道剩余时间,就不能显示进度吗?

知道总量与已完成量,就可以显示比例。预测不可靠时只省略秒数。如果连总量都未知,就采用不确定进度显示,并附上阶段名称和已处理数量。

11. 总结:剩余时间是预测,完成状态是结果

“剩余1秒”持续很久,并不是因为电脑不会计算1秒,而是因为它要根据已经观察到的工作量,估计还没发生的处理时间。再加上计数单位、目标查找、最后的阶段与界面更新延迟,情况就更加复杂。

如何解读迟迟不结束的进度显示依次确认比例在衡量什么、预测速度是否变化、是否还有其他阶段,以及停住的是显示还是实际处理。衡量什么的比例预测速度是否改变是否还剩其他阶段显示停滞还是处理停滞

图11:把对数字的不满,拆成可以核实的问题。

用户应关注阶段和变化,而不只盯着数字;开发者不要把工作量、预测和结果混在一起。相比一直猜准“还剩1秒”,更重要的是说明现在正在发生什么,以及哪些事情仍然未知。这才是好的进度显示应该做的事。

参考资料

  1. Microsoft Learn, LPPROGRESS_ROUTINE callback function. 复制进度回调所提供字节数的含义。 ↩

  2. Microsoft, The Old New Thing, Why does the copy dialog give such horrible estimates?. 2004年的解释,用来说明预测未来速度的困难,并非当前Windows内部实现的规格说明。 ↩

  3. Microsoft Learn, Slow SMB files transfer speed. 小文件传输反复产生的文件创建和通信开销。 ↩

  4. Microsoft Learn, Progress controls. 确定与不确定进度的控件。 ↩

  5. Microsoft Learn, Progress Bars. 桌面应用进度显示的设计指南。 ↩

  6. Microsoft Learn, File Caching. 文件缓存与写入行为。 ↩

  7. Microsoft Learn, FlushFileBuffers function. 把文件缓冲信息发送到设备的API。 ↩

  8. Microsoft Learn, Threading model. WPF的Dispatcher与UI线程响应能力。 ↩

  9. Microsoft Learn, Cancellation in Managed Threads. .NET的协作式取消。 ↩

  10. WHATWG, The progress element. HTML进度元素与不确定状态。 ↩

  11. W3C, WAI-ARIA 1.2: progressbar. 无障碍进度信息的名称与数值规则。 ↩

共享相同标签的最新文章。可以围绕相近的主题进一步加深理解。

与本文相近的主题页面。以本文为起点,可以进一步了解相关服务和其他文章。

本文与以下服务页面相关联,欢迎从最接近的入口查看。

常见问题

汇总了咨询这一主题时常见的问题。

一直显示剩余1秒,是发生故障了吗?
仅凭显示无法判断。需要区分速度预测失准、最后的处理阶段、界面没有更新和任务真正停滞。检查处理数量与日志是否变化,以及是否有等待确认的窗口,不要仅凭剩余1秒这个数字就强制结束应用。
进度达到99%,是否意味着只剩总时间的1%?
不是。99%的含义取决于它衡量的是数量、数据量还是阶段权重,而且每个单位的处理时间未必相同。进度百分比是工作量的比例,不是剩余时间本身。
圆形动画还在转,就能证明任务正常吗?
不能。如果界面动画与实际处理独立运行,任务处于等待状态时动画也可以继续转动。应把动画与处理数量、阶段变化、日志等实际进展的证据区分开。
无法准确估算剩余时间,就不能显示进度条吗?
可以显示。只要知道总量和已完成量,就能显示比例;速度不稳定时可以只省略剩余时间。如果连总量也未知,应显示阶段或已处理数量,而不是编造百分比。

作者简介

本文作者的个人简介页面。

Go Komura

小村软件有限公司 代表

以 Windows 软件开发、技术咨询与故障排查为中心,擅长难以复现的故障调查,以及既有资产仍在运行的项目。

返回博客列表