// Exercise 2: timeouts vs caller cancellation (medium) // // Implement WithTimeout(work, timeout, outer): // - Create a linked CancellationTokenSource from `outer`, CancelAfter(timeout), and pass its token to work. // - If the work is cancelled because the TIMEOUT elapsed, throw TimeoutException. // - If it's cancelled because the CALLER cancelled `outer`, let OperationCanceledException propagate. // - Dispose the CancellationTokenSource. // // Run with: dotnet run (inside a console project). using System; using System.Threading; using System.Threading.Tasks; class Program { static async Task WithTimeout(Func> work, TimeSpan timeout, CancellationToken outer) { // TODO return await work(CancellationToken.None); } static async Task Main() { int pass = 0, total = 0; void Check(string name, bool ok) { total++; if (ok) pass++; Console.WriteLine($"{(ok ? "PASS" : "FAIL")}: {name}"); } var fast = await WithTimeout(async ct => { await Task.Delay(20, ct); return 1; }, TimeSpan.FromMilliseconds(500), CancellationToken.None); Check("fast work returns", fast == 1); string kind = "none"; try { await WithTimeout(async ct => { await Task.Delay(2000, ct); return 2; }, TimeSpan.FromMilliseconds(100), CancellationToken.None); } catch (TimeoutException) { kind = "timeout"; } catch (OperationCanceledException) { kind = "cancelled"; } Check($"slow work times out (got {kind})", kind == "timeout"); using var outer = new CancellationTokenSource(100); kind = "none"; try { await WithTimeout(async ct => { await Task.Delay(2000, ct); return 3; }, TimeSpan.FromSeconds(5), outer.Token); } catch (TimeoutException) { kind = "timeout"; } catch (OperationCanceledException) { kind = "cancelled"; } Check($"caller cancellation propagates (got {kind})", kind == "cancelled"); var sw = System.Diagnostics.Stopwatch.StartNew(); try { await WithTimeout(async ct => { await Task.Delay(5000, ct); return 4; }, TimeSpan.FromMilliseconds(100), CancellationToken.None); } catch { } Check($"work is actually cancelled ({sw.ElapsedMilliseconds} ms)", sw.ElapsedMilliseconds < 1500); Console.WriteLine($"--- {pass}/{total} checks passed ---"); } }