Stopwatch.StartNew() 比 DateTime.Now 更准,因其底层调用高精度性能计数器(QueryPerformanceCounter),达纳秒级分辨率,而 DateTime.Now 仅10–15毫秒且受系统时间调整影响。

Stopwatch.StartNew() 为什么比 DateTime.Now 更准
因为 DateTime.Now 依赖系统时钟,分辨率通常只有 10–15 毫秒,且受系统时间调整影响;而 Stopwatch 底层调用高精度性能计数器(QueryPerformanceCounter),在大多数现代 Windows 系统上能达到纳秒级分辨率。
实操建议:
- 始终用
Stopwatch.StartNew()初始化,它内部已处理好计数器可用性检测,比先 new 再 Start 更安全 - 避免在 Stopwatch 运行中调用
Stopwatch.GetTimestamp()手动计算——容易误算起始偏移 - 如果跨进程或需要与日志时间对齐,再补记一次
DateTime.UtcNow,但计时不以此为准
Stopwatch.ElapsedMilliseconds 和 Elapsed.TotalMilliseconds 的区别
前者返回 long 类型的毫秒整数,会截断小数部分;后者是 double,保留全部精度(含微秒、纳秒换算后的余数)。
常见错误现象:用 ElapsedMilliseconds 测量短于 1ms 的操作,结果恒为 0,误判“没耗时”。
使用场景:
- 做粗略耗时判断(如 >100ms 就告警)→ 用
ElapsedMilliseconds - 压测、算法对比、性能回归分析 → 必须用
Elapsed.TotalMilliseconds或直接Elapsed.TotalTicks - 日志输出建议格式:
$"{sw.Elapsed.TotalMilliseconds:F3}ms",避免四舍五入掩盖真实波动
Stopwatch 在异步代码里怎么用才不翻车
Stopwatch 本身不是线程安全的,但它在单个线程内复用没问题;真正翻车点在于 await 后续执行可能切换线程,导致 Stop() 被不同线程调用——虽然不会抛异常,但逻辑已错乱。
实操建议:
- 不要把
Stopwatch实例传进async方法内部再 Start/Stop —— 无法保证同一线程 - 正确做法:在 await 前 Start,在 await 后立刻 Stop,且整个块不跨方法边界
- 示例:
var sw = Stopwatch.StartNew(); await SomeAsyncOperation(); sw.Stop(); // ✅ 安全:Start 和 Stop 在同一同步上下文完成
如果必须跨 await 分段计时(比如只测 IO 部分),改用 Environment.TickCount64 手动差值,但要注意它可能溢出(约 24.9 天)。
Stopwatch.IsHighResolution 为 false 怎么办
极少见,但在某些虚拟化环境、旧硬件或禁用高性能计数器的系统上会发生。此时 Stopwatch 会自动回落到 DateTime.UtcNow 级别精度,IsHighResolution 返回 false。
性能影响:计时误差可能达 10ms 以上,对微基准测试(micro-benchmark)完全失效。
可做的检查和应对:
- 运行时检查:
if (!Stopwatch.IsHighResolution) Console.WriteLine("警告:低精度计时器") - 开发阶段就在目标环境跑一次验证,别只在本地开发机测
- 生产环境若发现此问题,优先排查虚拟机配置(如 Hyper-V 的 “启用时间同步” 可能干扰)或 BIOS 中是否禁用了 TSC
精度这事,不是写了 Stopwatch 就自动高精度——它只是个门面,背后有没有真家伙,得看系统给不给。










