Stopwatch.StartNew()是最安全的启动方式,它一步到位自动调用Start(),避免因忘记Start()导致Elapsed为0的错误;复用时应使用Restart()而非Reset()+Start()。

Stopwatch.StartNew() 是最安全的启动方式
直接调用 Stopwatch.StartNew(),别先 new 再 Start。因为 Stopwatch 构造函数创建的是未启动状态,但有人会误以为它“已就绪”,结果忘了调用 Start() 就去读 Elapsed —— 这时返回的是 0,且毫无警告。
常见错误现象:ElapsedMilliseconds 始终为 0,或数值明显偏小;调试时发现 IsRunning 是 false 却没意识到。
-
Stopwatch.StartNew()一步到位,内部自动调用Start() - 如果必须复用实例(比如循环中反复测),记得每次测前调用
Restart(),而不是Reset()+Start()——Restart()是原子操作,避免竞态 - 不要在多线程里共享同一个
Stopwatch实例,除非你明确做了同步;不同线程请各自StartNew()
别用 DateTime.Now 做性能计时
DateTime.Now 分辨率低(通常 10–15ms),且受系统时钟调整影响。你看到的“耗时 2ms”可能是时钟没更新,实际跑了 12ms;深夜服务器 NTP 同步后还可能倒退,导致负数时间。
使用场景:仅适合粗略日志打点(比如“任务开始于 XX:XX”),绝不能用于 if (elapsed > 100) Log.Warn(...) 这类判断。
-
Stopwatch.IsHighResolution返回true才表示底层用了硬件计数器(如 TSC),这是毫秒级以下测量的前提 - 在容器或虚拟机里,
IsHighResolution可能为false,这时Stopwatch会回退到系统 API(如QueryPerformanceCounter),仍比DateTime可靠得多 - 别试图用
Stopwatch.GetTimestamp()手动算差值——除非你在写底层库,否则纯属增加出错概率
Stopwatch.ElapsedMilliseconds 和 Elapsed.TotalMilliseconds 的区别
前者是 long 类型,截断小数部分;后者是 double,带毫秒级小数(如 1.234)。多数时候你要的是整数毫秒,但注意:如果代码实际执行了 999.9ms,ElapsedMilliseconds 是 999,而 Elapsed.TotalMilliseconds 是 999.9 —— 对阈值判断(比如超 1s 告警)可能产生偏差。
性能影响:两者开销几乎无差别,都是读字段;但 TotalMilliseconds 涉及 double 转换,极端高频下(每微秒调一次)才需留意。
- 做超时判断、日志输出用
ElapsedMilliseconds更直观 - 需要亚毫秒精度分析(比如压测抖动)才用
Elapsed.TotalMilliseconds或ElapsedTicks - 别用
(int)stopwatch.Elapsed.TotalMilliseconds强转——可能溢出或四舍五入错误;要截断就用ElapsedMilliseconds
释放资源不是必须的,但 Stop() 之后不能再读 Elapsed
Stopwatch 不实现 IDisposable,不需要 using 或 Dispose()。它的资源就是几个字段,GC 自己就能收走。
容易踩的坑:调用 Stop() 后继续访问 Elapsed 没问题,但再调 Start() 会从当前值继续累加——如果你本意是“重测”,这就错了。
- 想重测一段逻辑?用
Restart(),它等价于Reset()+Start() - 想暂停后再续跑?可以
Stop(),但后续Start()是合法的;只是要注意IsRunning状态变化 - 别在
finally里无条件Stop()—— 如果 stopwatch 根本没Start()过,Stop()会静默失败,且Elapsed仍是 0
Stopwatch 的核心就两条:用 StartNew() 启动,用 ElapsedMilliseconds 取整数毫秒。其它所有操作,都是为绕开某个具体场景下的陷阱——比如复用、暂停续跑、或者跨线程。真正难的不是怎么写,而是想清楚“这段代码到底要不要被重复计时”“它是否可能被中断”“我关心的是整毫秒还是更细”。这些决定比语法重要得多。










