using await Task.Delay in a for kills performance
Data Structures & Algorithms practice on Codemia
Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.
Introduction
Using await Task.Delay() inside a loop creates a pause on every iteration, which multiplies the total execution time by the number of iterations. A 100ms delay in a 1000-iteration loop adds 100 seconds of waiting. The performance impact comes from the timer overhead, context switches, and the accumulated delay time. The fix depends on your intent: for rate limiting, use SemaphoreSlim or Parallel.ForEachAsync with a degree of parallelism. For periodic polling, use PeriodicTimer. For batch processing with throttling, delay between batches rather than between individual items.
The Problem
Each await Task.Delay(100) adds 100ms plus timer resolution overhead (~15ms on Windows). For 1000 items, the total delay alone is ~115 seconds.
Fix 1: Remove Unnecessary Delays
If the delay was added to "avoid overloading" a service, remove it and let the service's built-in rate limiting or backpressure handle throttling.
Fix 2: Delay Between Batches, Not Items
Batching reduces delay count from N to N/batchSize, dramatically cutting total wait time while still providing backpressure.
Fix 3: SemaphoreSlim for Concurrency Control
SemaphoreSlim limits concurrency without artificial delays. Items process as fast as possible within the concurrency limit.
Fix 4: Parallel.ForEachAsync (.NET 6+)
Parallel.ForEachAsync handles concurrency throttling internally. It is the cleanest solution in .NET 6+ for bounded concurrent async work.
Fix 5: PeriodicTimer for Polling
PeriodicTimer fires at consistent intervals regardless of how long the work takes, while Task.Delay adds drift (interval + processing time).
Fix 6: Channel-Based Producer-Consumer
Channels provide natural backpressure without delays. The bounded capacity limits how far ahead the producer can get.
Common Pitfalls
- Using Task.Delay for rate limiting:
Task.Delayis a blunt instrument that adds fixed waits regardless of actual load. UseSemaphoreSlimorParallel.ForEachAsyncfor proper concurrency control. - Timer resolution on Windows:
Task.Delay(1)does not delay 1ms — the Windows timer resolution is ~15ms. Short delays are rounded up, making per-item delays even more expensive than expected. - Task.Delay(0) is not free:
Task.Delay(0)yields to the scheduler but still has overhead from timer allocation and callback registration. UseTask.Yield()if you just want to yield. - Accumulated drift in polling loops:
await Task.Delay(interval)after work creates drift — the actual interval is delay + processing time. UsePeriodicTimerfor consistent intervals. - Delaying to "fix" a race condition: Adding
Task.Delay(100)to work around timing issues is fragile. Use proper synchronization (SemaphoreSlim, lock, Channel) instead of relying on delays.
Summary
await Task.Delayin a loop multiplies total time by iteration count — avoid per-item delays- Batch items and delay between batches to reduce total delay by the batch size factor
- Use
SemaphoreSlimorParallel.ForEachAsyncfor concurrency control without artificial delays - Use
PeriodicTimerfor polling instead ofwhile (true) { ... await Task.Delay(); } - Use
Channel<T>for producer-consumer patterns with natural backpressure - Only use
Task.Delaywhen you genuinely need a time-based pause, not for rate limiting
Related reading
- Using circular permutations to reduce Traveling Salesman complexity
- Using Dijkstra's algorithm to find a path that can carry the most weight
- USING Keyword vs ON clause - MYSQL
- Using libcurl in a multithreaded environment causes VERY slow performance related to DNS lookup
- Using await within a Promise
- Using BindingOperations.EnableCollectionSynchronization
- Using c++ .lib file in WCF or c# wrapper?
- Using Case/Switch and GetType to determine the object

DSA Fundamentals
Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
Data Structures & Algorithms practice on Codemia
Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.