Tasks
Task is C#s unit of asynchronous work. async makes a method return a Task (or Task
Task patterns: await, WhenAll, cancellation, ValueTask
EXAMPLE
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
class TaskDemo
{
static readonly HttpClient http = new();
// 1) async / await — simple and linear
static async Task<string> FetchTitleAsync(string url, CancellationToken ct)
{
using var res = await http.GetAsync(url, ct);
res.EnsureSuccessStatusCode();
var html = await res.Content.ReadAsStringAsync(ct);
var i = html.IndexOf("<title>", StringComparison.Ordinal);
var j = html.IndexOf("</title>", StringComparison.Ordinal);
return i >= 0 && j > i ? html[(i + 7)..j] : "(no title)";
}
static async Task Main()
{
// 2) Concurrent fan-out: kick all tasks, then await all
var urls = new[] { "https://example.com", "https://example.org", "https://example.net" };
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
var tasks = Array.ConvertAll(urls, u => FetchTitleAsync(u, cts.Token));
var titles = await Task.WhenAll(tasks);
foreach (var t in titles) Console.WriteLine(t);
// 3) WhenAny — race two operations
var fast = Task.Delay(100).ContinueWith(_ => "fast");
var slow = Task.Delay(500).ContinueWith(_ => "slow");
Console.WriteLine(await await Task.WhenAny(fast, slow)); // "fast"
// 4) Cancellation — flow the token through, do not swallow it
try
{
using var quickCts = new CancellationTokenSource(50);
await SlowAsync(quickCts.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("cancelled cleanly");
}
// 5) ConfigureAwait(false) in libraries — skip the SyncContext capture
// UI: omit it. Library: include it. Console / minimal API: irrelevant.
// 6) ValueTask for hot paths that often complete synchronously
Console.WriteLine(await CachedReadAsync("a"));
}
static async Task SlowAsync(CancellationToken ct)
{
await Task.Delay(1000, ct);
}
static readonly System.Collections.Concurrent.ConcurrentDictionary<string, string> Cache = new();
static ValueTask<string> CachedReadAsync(string key)
=> Cache.TryGetValue(key, out var v)
? new ValueTask<string>(v) // sync path: no allocation
: new ValueTask<string>(LoadAsync(key)); // async path: one Task
static async Task<string> LoadAsync(string key)
{
await Task.Delay(10);
var v = "v-" + key;
Cache[key] = v;
return v;
}
}
Why it matters
Always thread a CancellationToken through every async API you write. The discipline pays off the moment a user hits Cancel, a request times out, or a worker shuts down — without it you have leaked work that cannot be stopped, which becomes the dominant source of zombie tasks in production.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
var t1 = Task.Run(() => Work(1)); var t2 = Task.Run(() => Work(2)); await Task.WhenAll(t1, t2);Try it Yourself »
Discussion
Loading…