The async and await keywords let you write asynchronous code that reads like synchronous code. Marking a method async does not make it run on a separate thread by itself; rather, it allows the compiler to generate a state machine that handles continuations. When the runtime hits an await on a Task or Task
Choosing between Task and Thread matters. A Thread is a low-level OS thread, expensive to create (each carries roughly a 1 MB stack), and you manage its lifecycle directly. A Task is a higher-level abstraction over work that runs on the thread pool, cheap to create, with the runtime reusing pool threads. Prefer Task for almost everything; reserve Thread for long-lived, identity-bearing execution such as dedicated background loops with IsBackground. For coordinating multiple operations, Task.WhenAll awaits a collection of tasks concurrently and completes when all are done, while Task.WhenAny completes as soon as any one task finishes, making it useful for implementing timeouts or racing multiple sources. CancellationToken flows cancellation through async pipelines: a CancellationTokenSource provides the token, async methods periodically check IsCancellationRequested or call ThrowIfCancellationRequested, and OperationCanceledException surfaces to the caller. IAsyncDisposable, used with await using, is the asynchronous counterpart of IDisposable for resources that require async cleanup. Many types implement both IDisposable and IAsyncDisposable, and the runtime calls the right one based on the using form.
Several pitfalls are worth knowing. Exceptions in async methods are captured in the returned Task, so a try/catch with await is sufficient; without await, the exception goes unobserved. ExceptionDispatchInfo.Capture(ex).Throw() preserves the original stack trace when rethrowing across thread or async boundaries, so prefer it over the older throw ex pattern, which resets the stack trace to the current line. async void is dangerous outside top-level event handlers because exceptions go directly to the synchronization context (or crash the process) and cannot be awaited. Thread.Sleep blocks the current thread for a duration, while await Task.Delay yields the thread back to the pool, keeping the application responsive. TaskCompletionSource
Parallelism in .NET comes in several flavors. Parallel.ForEach executes foreach iterations across multiple threads, ideal for CPU-bound work, while PLINQ (Parallel LINQ) is the parallel version of LINQ, enabled with AsParallel(); both partition the work across cores but suffer overhead on small collections, so gains diminish quickly. For I/O-bound work, async/await with Task.WhenAll is the right tool, since it does not tie up threads while waiting. Be cautious with shared state in parallel code and prefer Concurrent collections or synchronization.