CodingNeed.

Production engineering workshop · 45 MIN

C# · Bound asynchronous work and propagate cancellation

Bound concurrency independently from the size of the input.

Parallel.ForEachAsync limits simultaneous delegate executions. CancellationToken must be passed into the delegate’s asynchronous operation; cancellation is cooperative. Store each result at its input index to preserve order without a shared append operation. Exceptions cause the returned task to fail; decide explicitly whether partial results are useful. This implementation expects a materialized array, so it does not claim constant-memory streaming.

First reproduce the normal path. Then force a failure at each boundary and inspect what remains true.

Read the example

using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

public static class Pipeline {
  public static async Task<int[]> MapAsync(
      int[] input, int concurrency,
      Func<int,CancellationToken,Task<int>> transform,
      CancellationToken ct) {
    if (concurrency < 1) throw new ArgumentOutOfRangeException(nameof(concurrency));
    var result = new int[input.Length];
    await Parallel.ForEachAsync(Enumerable.Range(0,input.Length),
      new ParallelOptions { MaxDegreeOfParallelism=concurrency, CancellationToken=ct },
      async (index, token) => { result[index] = await transform(input[index], token); });
    return result;
  }
}
// Example inside async Main:
// var result = await Pipeline.MapAsync(new[]{1,2,3}, 2,
//   async (n, ct) => { await Task.Delay(10,ct); return n*n; }, CancellationToken.None);
Check the expected output
Review the behavior in the stated project environment.

Your challenge

In a .NET console project, implement the function below and test an empty array, cancellation during Task.Delay, and a delegate that throws. Replace the sample transform with an HttpClient operation and propagate the supplied token. Use an injected managed HttpClient in a server project.

Solution cost: O(n) bookkeeping plus bounded asynchronous work. time · O(n + c) output and concurrency state. space

Common trap

Task.WhenAll over all input items does not itself bound the number of operations started.

Study the project implementation
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

public static class Pipeline {
  public static async Task<int[]> MapAsync(
      int[] input, int concurrency,
      Func<int,CancellationToken,Task<int>> transform,
      CancellationToken ct) {
    if (concurrency < 1) throw new ArgumentOutOfRangeException(nameof(concurrency));
    var result = new int[input.Length];
    await Parallel.ForEachAsync(Enumerable.Range(0,input.Length),
      new ParallelOptions { MaxDegreeOfParallelism=concurrency, CancellationToken=ct },
      async (index, token) => { result[index] = await transform(input[index], token); });
    return result;
  }
}
// Example inside async Main:
// var result = await Pipeline.MapAsync(new[]{1,2,3}, 2,
//   async (n, ct) => { await Task.Delay(10,ct); return n*n; }, CancellationToken.None);

Further reading: Official documentation

Essential cookies keep your account signed in. Optional analytics is not configured on this site. Your choice does not affect access to lessons.

Read the Privacy Policy. You can change this choice in the footer.