// Exercise 2: a bounded producer/consumer pipeline with Channels (medium) // // Implement ProcessAsync(items, workers, handler): // - Create a bounded channel (capacity 10). // - Start `workers` consumer tasks that read with ReadAllAsync and call `await handler(item)`, adding the // returned value to a shared total (use Interlocked.Add). // - The producer writes all items, then completes the writer. // - Await all consumers and return the total. // The handler tracks how many calls run at once; never more than `workers` may run concurrently, and with // 4 workers the 40 items (50 ms each) must finish well under the sequential 2 seconds. // // Run with: dotnet run (inside a console project). using System; using System.Linq; using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; class Program { static async Task ProcessAsync(int[] items, int workers, Func> handler) { long total = 0; foreach (var i in items) total += await handler(i); // TODO: replace with a channel + `workers` consumers return total; } 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}"); } int running = 0, maxRunning = 0; async Task Handler(int x) { int now = Interlocked.Increment(ref running); int seen; while ((seen = Volatile.Read(ref maxRunning)) < now && Interlocked.CompareExchange(ref maxRunning, now, seen) != seen) { } await Task.Delay(50); Interlocked.Decrement(ref running); return x * 2; } var items = Enumerable.Range(1, 40).ToArray(); var sw = System.Diagnostics.Stopwatch.StartNew(); long result = await ProcessAsync(items, 4, Handler); long ms = sw.ElapsedMilliseconds; Check($"correct total ({result})", result == 1640); Check($"bounded concurrency (max {maxRunning})", maxRunning <= 4 && maxRunning >= 2); Check($"runs concurrently ({ms} ms)", ms < 1200); Console.WriteLine($"--- {pass}/{total} checks passed ---"); } }