stopwatch.startnew()后必须调用stop()才能获取准确耗时,否则elapsedmilliseconds持续累加;倒计时应基于起始时间点计算并截断负值,避免直接减法;高频测量宜复用实例而非反复创建。
stopwatch.startnew() 之后必须调用 stop() 才能拿到准确耗时
很多人用 stopwatch 测倒计时或耗时,却忘了 stop() 这一步——结果 elapsedmilliseconds 一直涨,甚至在多次读取时返回越来越大的值。这是因为 stopwatch 默认是“运行中”状态,不手动 stop(),它就持续计时。
常见错误现象:Stopwatch.ElapsedMilliseconds 每次调用都变大,哪怕逻辑只执行一次;或者在循环里反复读取却没重置,数值不断累加。
- 正确做法:启动后,在你想“定格时间”的位置立刻调用
Stop() - 如果需要多次测量(比如每帧刷新倒计时),要么每次
Restart(),要么新建实例(Stopwatch.StartNew()) -
IsRunning属性可用来判断当前是否还在跑,调试时很有用
示例:
var sw = Stopwatch.StartNew(); // ... 执行一段操作 sw.Stop(); // 必须加! long ms = sw.ElapsedMilliseconds;
倒计时逻辑别直接依赖 Stopwatch.ElapsedMilliseconds 做减法
用 Stopwatch 实现倒计时(比如 10 秒倒数),容易想当然写成 remaining = total - sw.ElapsedMilliseconds。这在单次计算时没问题,但一旦涉及 UI 刷新、异步等待或跨线程,就会出问题:时间跳变、负数、卡顿后疯狂刷屏。
根本原因是 ElapsedMilliseconds 是单调递增的“流逝量”,不是“当前时刻”。它不感知你想要的倒计时节奏,也不处理精度抖动和线程调度延迟。
- 真正稳定的倒计时,应该基于一个起始时间点 + 固定总时长,用
Math.Max(0, totalMs - sw.ElapsedMilliseconds)截断负值 - UI 更新建议用
DispatcherTimer或Task.Delay()控制节奏,而不是靠高频轮询Stopwatch - 如果倒计时要暂停/继续,别反复
Start()/Stop()—— 记录暂停时的Elapsed,恢复时用新Elapsed减去它
不要在高频率循环里反复 new Stopwatch 或调用 StartNew()
有人为“每次测一段代码”在 for 循环里写 var sw = Stopwatch.StartNew(); ... sw.Stop();。短时间看不出问题,但频繁创建对象会增加 GC 压力,尤其在 .NET Framework 或低配设备上可能拖慢整体性能。
更隐蔽的问题是:Stopwatch.StartNew() 虽然快,但底层仍需查询高性能计数器(QueryPerformanceCounter),在某些虚拟化环境或旧硬件上开销比预期高。
- 高频测量场景(如游戏帧耗时统计),复用同一个
Stopwatch实例,用Restart()代替反复 new - 确认是否真需要微秒级精度——多数业务逻辑用
DateTime.UtcNow差值已足够,且无 GC 开销 - .NET 6+ 中
Stopwatch.GetTimestamp()更轻量,适合嵌入式或极致性能场景,但需自己换算成毫秒
Stopwatch 在不同平台上的精度差异实际影响倒计时显示
Stopwatch.IsHighResolution 返回 true 不代表“一定准到毫秒”。Windows 上通常基于 QueryPerformanceCounter,Linux/macOS 则依赖 clock_gettime(CLOCK_MONOTONIC)。但在容器、WSL、老旧 BIOS 或某些云主机上,这个计数器可能被虚拟化层插桩,导致跳变或漂移。
表现就是:倒计时偶尔快进 1–2 秒,或卡住半秒后突然跳变。这不是代码 bug,而是底层时钟源不可靠。
- 对用户可见的倒计时(如 UI 倒数),建议用
DateTime.UtcNow做主时间轴,仅用Stopwatch做“相对偏移校准” - 避免把
Stopwatch当作绝对时间源——它只保证“相对准确”,不保证“绝对同步” - 跨进程/跨机器协同倒计时?必须用 NTP 校准的系统时间,
Stopwatch完全不适用
事情说清了就结束。











