Concurrency & Delay in C#
Pause execution with Task.Delay, run tasks with Task.Run, coordinate with Task.WhenAll / Task.WhenAny, use CancellationToken, and prevent race conditions with lock and Interlocked.
Waiting & Async Execution
1. Wait for N Milliseconds
Use `await Task.Delay(ms)` to pause without blocking the thread.
2. Run a Function Asynchronously
`Task.Run()` offloads CPU-bound work to a thread-pool thread.
Coordinating Async Operations
1. Wait for Multiple Async Tasks (Task.WhenAll)
`Task.WhenAll` runs tasks concurrently and waits until all finish.
2. Wait for At Least One Task (Task.WhenAny)
`Task.WhenAny` completes as soon as the first task finishes.
Task Chaining
1. ContinueWith() — Chain After Completion
`ContinueWith` runs a callback when the preceding task finishes.
2. Handling Exceptions with ContinueWith()
When a task faults, `ContinueWith` still runs. Access the exception via `t.Exception` — but you must observe it, otherwise the exception is silently swallowed. Use `TaskContinuationOptions` to run callbacks only on specific outcomes.
3. Handling Errors in Async Code
Use try/catch inside async methods — exceptions propagate naturally.
4. finally in Async Code
`finally` blocks work as expected in async methods.
5. ContinueWith vs async/await
`async/await` is the modern preferred style — cleaner than ContinueWith chains.
Passing Data Between Async & Sync Code
1. Passing Data from an Async Method
Return `Task<T>` from async methods and `await` to extract the value.
Timers & Intervals
1. Perform Operation After Delay
`Task.Delay` + `await` is the idiomatic async delay.
2. Perform Operation at Every Interval
`PeriodicTimer` (.NET 6+) is the modern tick-based interval — no callback required.
Measuring Time Intervals
1. Calculating Time Interval
`Stopwatch` from `System.Diagnostics` measures elapsed time with high precision.
Parallel Execution
1. Running Tasks in Parallel with Task.Run
Fire multiple `Task.Run` calls without awaiting immediately, then collect with `Task.WhenAll`.
2. Raw Thread Creation
Create a low-level OS thread using `new Thread(...)`. Use `Start()` to launch and `Join()` to wait for completion.
3. Parallel.For and Parallel.ForEach
`Parallel.For` and `Parallel.ForEach` parallelise loops across all CPU cores.
Thread Safety & Synchronization
1. Race Condition Problem
Two threads modifying shared state simultaneously produces incorrect results.
2. Fix with Interlocked
`Interlocked.Increment` performs atomic increment — no lock needed for simple counters.
3. Fix with lock
`lock(obj)` ensures only one thread executes the protected block at a time.
4. Deadlock — Problem and Prevention
A deadlock occurs when two threads each wait for a lock held by the other — they block forever. Fix by always acquiring locks in a consistent order.
5. SemaphoreSlim — Limit Concurrency
`SemaphoreSlim` limits how many tasks can execute a section concurrently.