Stopwatch.StartNew() 是唯一推荐的初始化方式,它内部完成实例化、启动及计数器检测,避免漏调 Start() 导致 ElapsedMilliseconds 恒为 0;应优先使用 Elapsed.TotalMilliseconds 获取微秒级精度,单次短操作测量必用此属性。

Stopwatch.StartNew() 是唯一推荐的初始化方式
别先 new Stopwatch() 再手动调用 Start(),这容易漏掉启动步骤,导致 ElapsedMilliseconds 始终为 0。静态方法 Stopwatch.StartNew() 内部已完成实例化 + 启动 + 计数器可用性检测,语义明确、零遗漏风险。
常见错误现象:sw.ElapsedMilliseconds 总是返回 0,但代码明明执行了;调试时发现 sw.IsRunning 一直是 false。
- 正确写法:
var sw = Stopwatch.StartNew(); - 错误写法:
var sw = new Stopwatch(); // 忘了 sw.Start() - .NET Framework 4.5+ 才支持
Restart(),老版本只能Reset()+Start()
ElapsedMilliseconds 和 Elapsed.TotalMilliseconds 别混用
ElapsedMilliseconds 返回 long 类型整数毫秒,截断小数部分;Elapsed.TotalMilliseconds 返回 double,保留微秒级精度(比如 12.345678 ms)。性能敏感场景(如算法对比、基准测试)必须用后者,否则可能掩盖真实差异。
使用场景举例:循环执行 100 次某操作,单次耗时约 0.8 ms —— 用 ElapsedMilliseconds 全部显示为 0,而 Elapsed.TotalMilliseconds 能稳定给出 0.792~0.815 这样的值。
- 测单次短操作:优先用
sw.Elapsed.TotalMilliseconds - 只关心粗略整数毫秒(如 UI 响应提示):可用
sw.ElapsedMilliseconds - 需要纳秒级精度?直接读
sw.ElapsedTicks,再除以Stopwatch.Frequency
重复计时别忘了 Stop() 或 Restart()
Stopwatch 不会自动停。如果在未调用 Stop() 的情况下就去读 Elapsed,结果仍是当前运行时间;若紧接着又调用 Start(),会报 InvalidOperationException:“Cannot start a stopped stopwatch”。
容易踩的坑:在 while 循环里反复 Start() 却没 Stop(),或者误以为 Reset() 会自动停止计时器(它不会)。
- 单次测量流程固定为:
StartNew()→ 执行代码 →Stop() - 多次测量同一段逻辑:用
sw.Restart()(推荐)或sw.Reset(); sw.Start(); - 调试时加一句
Console.WriteLine(sw.IsRunning);,能快速确认状态
Stopwatch 不是 DateTime,别拿它对齐日志时间
Stopwatch 底层依赖系统高性能计数器(如 Windows 的 QueryPerformanceCounter),不受系统时间调整影响,也不和 DateTime.UtcNow 对齐。它只回答“这段代码跑了多久”,不回答“它是什么时候跑的”。
如果你需要把耗时和日志时间戳关联(例如排查某次请求从收到到响应花了多久),得额外记一次 DateTime.UtcNow,但计时本身绝不能靠它算差值。
- 禁止写:
var start = DateTime.UtcNow; ... var elapsed = (DateTime.UtcNow - start).TotalMilliseconds; - 正确组合:
var startLogTime = DateTime.UtcNow; var sw = Stopwatch.StartNew(); ... sw.Stop(); Console.WriteLine($"{startLogTime:O} | {sw.Elapsed.TotalMilliseconds:F3}ms"); - 跨进程/高并发下,Stopwatch 仍可靠;但
DateTime.Now可能因 NTP 同步跳变,绝对不可用于差值计算
实际项目中,最常被忽略的是 IsRunning 状态检查和 TotalMilliseconds 的精度取舍——尤其在压测或微基准测试时,差一个数量级的误差都可能归因于这里。











