Understand Task in easy words: run work now or later, wait for it to finish, get a result, handle errors, cancel, and combine many tasks.
Quick Summary
Task = a promise that some work will finish in the future.
async/await = easy keywords to wait for a Task without blocking the thread.
CPU work → use Task.Run. I/O work (file, db, web) → use the API's built‑in async methods.
Parallel = many things at the same time. Async = don't block while waiting. They can be used together or separately.
1) Task Basics
What is a Task? A Task represents work that is happening or will happen. You can await it to get the result when it's done.
Task → no value, just completion.
Task<T> → returns a value of type T.
ValueTask → like Task, with less allocation in certain high‑perf cases.
Easy rule: Library code may use ValueTask. App code can use Task 99% of the time.
Minimal Example
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
Task work = Task.Delay(1000); // pretend work (1 sec)
await work; // wait without blocking
Console.WriteLine("Done!");
}
}
2) Create & Start Tasks
Prefer the Task.Run pattern for CPU-bound work. For I/O, use the API's own Async method (like ReadAsync, GetAsync).
CPU-bound (use Task.Run)
using System;
using System.Linq;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
// Heavy calculation on a background thread
int sum = await Task.Run(() => Enumerable.Range(1, 10_000_000).Sum());
Console.WriteLine($"Sum = {sum}");
}
}
I/O-bound (use native async APIs)
using System;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
static readonly HttpClient http = new HttpClient();
static async Task Main()
{
string html = await http.GetStringAsync("https://example.com");
Console.WriteLine($"Downloaded {html.Length} chars");
}
}
3) Returning Values
Use Task<T> to return a value. Await it to get the value.
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
int answer = await ComputeAsync(21);
Console.WriteLine($"Answer is {answer}");
}
static async Task<int> ComputeAsync(int x)
{
await Task.Delay(300); // simulate work
return x * 2;
}
}
4) async / await (the easy way to wait)
async marks a method that returns Task or Task<T>.
await pauses the method until the Task finishes, without blocking the thread.
Use ConfigureAwait(false) in library code to avoid capturing context (advanced).
using System;
using System.IO;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
await File.WriteAllTextAsync("out.txt", "Hello async!");
string text = await File.ReadAllTextAsync("out.txt");
Console.WriteLine(text);
}
}
5) Combine Tasks: WhenAll / WhenAny
Run in parallel and wait for all
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
Task t1 = Task.Delay(500);
Task t2 = Task.Delay(800);
Task t3 = Task.Delay(200);
await Task.WhenAll(t1, t2, t3);
Console.WriteLine("All finished!");
}
}
Wait for the first one
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
Task t1 = Task.Delay(500);
Task t2 = Task.Delay(800);
Task first = await Task.WhenAny(t1, t2);
Console.WriteLine("The first task finished!");
}
}
6) Cancellation
Use CancellationToken to stop work politely.
using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
using var cts = new CancellationTokenSource();
var token = cts.Token;
var task = DoWorkAsync(token);
cts.CancelAfter(500); // cancel after 0.5s
try
{
await task;
}
catch (OperationCanceledException)
{
Console.WriteLine("Canceled!");
}
}
static async Task DoWorkAsync(CancellationToken token)
{
for (int i = 0; i < 10; i++)
{
token.ThrowIfCancellationRequested();
await Task.Delay(200, token);
Console.WriteLine($"Step {i}");
}
}
}
7) Error Handling
Exceptions inside a Task are re-thrown when you await it. Wrap with try/catch.
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
try
{
await MightFailAsync();
}
catch (Exception ex)
{
Console.WriteLine($"Oops: {ex.Message}");
}
}
static async Task MightFailAsync()
{
await Task.Delay(200);
throw new InvalidOperationException("Something went wrong");
}
}
Multiple tasks failing? With Task.WhenAll, exceptions are wrapped in AggregateException. Catch it and inspect InnerExceptions.
8) Task vs Thread (easy view)
Thread: an OS worker you manage directly. Heavier, fewer per app.
Task: a .NET job scheduled on the thread pool. Lighter, easier to compose.
async I/O: often uses zero/very few threads while waiting.
Use Task for most app work, especially when you need to wait for I/O without blocking.
Use Thread only for special cases (thread affinity, STA, long-running dedicated worker).
9) Common Pitfalls
Blocking with .Result / .Wait() can deadlock in UI/ASP.NET. Prefer await.
Don't use Task.Run for I/O — it adds useless threads. Use the API's Async methods.
Avoid async void except for event handlers.
Remember to pass CancellationToken down to other methods.
10) Practice Snippets
Download many URLs in parallel (WhenAll)
using System;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
static readonly HttpClient http = new HttpClient();
static async Task Main()
{
string[] urls = {
"https://example.com",
"https://www.microsoft.com",
"https://learn.microsoft.com"
};
Task<int>[] jobs = urls.Select(async u =>
{
string s = await http.GetStringAsync(u);
return s.Length;
}).ToArray();
int[] sizes = await Task.WhenAll(jobs);
Console.WriteLine(string.Join(", ", sizes));
}
}
Progress reporting
using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
var progress = new Progress<int>(p => Console.WriteLine($"{p}%"));
await DoWorkAsync(progress);
}
static async Task DoWorkAsync(IProgress<int> progress)
{
for (int i = 1; i <= 5; i++)
{
await Task.Delay(250);
progress.Report(i * 20);
}
}
}
Timeout with CancellationToken
using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(1));
try
{
await SlowAsync(cts.Token);
Console.WriteLine("Completed");
}
catch (OperationCanceledException)
{
Console.WriteLine("Timed out");
}
}
static async Task SlowAsync(CancellationToken token)
{
await Task.Delay(3000, token);
}
}