Гарантирует ли метод Task.Delay точное время ожидания или есть люфт?
Что проверяют
Понимаете ли вы, что стоит за await Task.Delay, и не пытаетесь ли строить на нём точный таймер. Следом обычно спрашивают, чем он отличается от Thread.Sleep и как сделать периодическую задачу без накопления сдвига.
Как отвечать
Короткий ответ: точности нет. Заданное значение это ориентир снизу, фактическая задержка обычно больше, а точнее разрешения таймера она не бывает. Дальше объясните, откуда берётся люфт:
- таймер ОС:
Task.Delayвозвращает задачу, которую завершает системный таймер, и его разрешение зависит от платформы. На Windows по умолчанию это около 15 мс, поэтомуTask.Delay(1)на практике ждёт примерно 15 мс, в документации это сказано прямо. На Linux разрешение мельче; - продолжение после await: когда таймер сработал, код после
awaitещё нужно запустить. Если естьSynchronizationContext(WPF, WinForms), продолжение встаёт в очередь UI-потока и ждёт, пока тот освободится. Без контекста оно уходит в пул потоков, и при голодании пула ожидание заметно растягивается; - нагрузка: паузы сборщика мусора и занятый процессор добавляют задержку сверху.
Отличие от Thread.Sleep: Sleep держит поток заблокированным, а Task.Delay его отпускает, и поток занимается другой работой.
Цикл while (true) { Work(); await Task.Delay(1000); } дрейфует: к интервалу каждый раз добавляется время работы. Для периодических задач лучше PeriodicTimer (есть с .NET 6) и его WaitForNextTickAsync, он держит ритм тиков. В .NET 8 у Task.Delay появилась перегрузка с TimeProvider, в тестах её подменяют на FakeTimeProvider и не ждут реальное время.
Если нужна настоящая миллисекундная точность, это уже не задача для Task.Delay: берут занятое ожидание со Stopwatch или специализированные таймеры и платят за это процессором.
Где спотыкаются
Говорят, что на ненагруженной машине задержка точная. Разрешение таймера никуда не девается даже на пустом сервере.
Вызывают Task.Delay без await. Задача создаётся, а код идёт дальше без всякой паузы.
Забывают передать CancellationToken. Долгий Delay в фоновом сервисе тогда задерживает остановку приложения.