iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Tasks

Task is C#s unit of asynchronous work. async makes a method return a Task (or Task) without changing the call site, and await unwraps it without blocking the thread. The runtime hops the continuation back to the captured synchronisation context — exactly what UI code wants, and exactly what library code does not.

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…