.NET
Task.Delay
timing issues
asynchronous programming
software development

Task.Delay in .net fires 125ms early

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Task.Delay timing in .NET is best-effort, not hard real-time. Perceived “early firing” often comes from timer resolution, scheduling jitter, clock measurement method, or logging precision issues.

This article explains what to expect and how to measure/mitigate drift.

Core Sections

1) Basic behavior

csharp
var sw = Stopwatch.StartNew();
await Task.Delay(1000);
Console.WriteLine(sw.ElapsedMilliseconds);

Elapsed time varies around target due to scheduling.

2) Measure with Stopwatch

DateTime.Now can be affected by clock adjustments; use Stopwatch for interval accuracy.

3) Event loop and thread pool effects

Under load, continuations may resume late. Under certain coarse logging conditions, they may appear early due to measurement granularity.

4) Periodic timing pattern

csharp
1var period = TimeSpan.FromSeconds(1);
2var next = Stopwatch.GetTimestamp();
3while (true) {
4    next += (long)(period.TotalSeconds * Stopwatch.Frequency);
5    var remaining = TimeSpan.FromSeconds((next - Stopwatch.GetTimestamp()) / (double)Stopwatch.Frequency);
6    if (remaining > TimeSpan.Zero) await Task.Delay(remaining);
7    Tick();
8}

Compensating loops reduce accumulated drift.

5) Real-time requirements

For strict timing guarantees, managed async timers may be insufficient; use dedicated real-time systems/hardware timers.

6) Production checklist for timing-sensitive async scheduling

A correct code snippet is only the baseline. To make this approach durable in production, define explicit acceptance checks around correctness, reliability, and operational behavior. Correctness means the output should match known-good fixtures for both normal and edge-case inputs. Reliability means failures are predictable and observable, with clear error messages and no silent degradation paths. Operational behavior means the implementation performs within expected latency and resource usage under realistic load, not only under tiny test data. Teams that skip this validation layer often ship logic that appears correct in local testing but fails under real traffic or environmental differences.

Document assumptions near the implementation: runtime version, dependency versions, required environment variables, and external system expectations. Many regressions are caused by version drift or configuration changes, not by algorithmic mistakes. If this workflow depends on filesystem paths, network resources, security credentials, or framework defaults, codify those requirements in code comments or adjacent documentation so they are visible during review. Add one deterministic smoke test that executes this path end-to-end and one failure-mode test that proves errors are surfaced with enough context for quick triage.

A practical release sequence is:

  1. Run static checks and unit tests in CI.
  2. Execute a smoke test with representative input shape and size.
  3. Trigger one expected failure mode and verify logs/metrics.
  4. Deploy with staged rollout or feature flag where possible.
  5. Monitor stabilization metrics before broad rollout.
bash
1# Example delivery workflow
2make lint
3make test
4./scripts/smoke_check.sh

Ownership and rollback should also be explicit. Define who responds when this component fails, what thresholds trigger rollback, and which fallback behavior is acceptable for users. If the workflow is business-critical, keep a concise runbook that includes common failure signatures and first-response steps. This reduces mean time to recovery and prevents repeated rediscovery of the same diagnostics.

Finally, maintain a brief limitations note. State what this approach intentionally does not solve and where alternative patterns are preferred. This prevents accidental overuse and keeps architecture decisions grounded in explicit tradeoffs. Revisit this checklist after framework, runtime, or infrastructure upgrades because previously safe assumptions can change when defaults evolve.

Common Pitfalls

  • Expecting millisecond-perfect timing from Task.Delay.
  • Measuring intervals with wall-clock APIs instead of Stopwatch.
  • Ignoring scheduler load and GC pauses in timing tests.
  • Using fixed Task.Delay(period) in loops and accumulating drift.
  • Treating logger timestamps as authoritative interval measurements.

Summary

Task.Delay is cooperative scheduling, not precise real-time timing. Use Stopwatch for measurement, compensate drift in periodic loops, and choose specialized timing strategies when strict precision is mandatory.


Related reading
Course
Intermediate
27 lessons
14 hours
OOD Fundamentals

Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.