Async .NET · Lesson 11

Bounded batches

Limit outstanding work instead of starting every task at once.

How it works

A simple batching strategy limits how many independent operations are outstanding at once. Chunk divides the prepared identifiers into groups of two. Each group is processed concurrently with WhenAll, and the outer loop waits for the group before starting the next. This is easy to understand and bounds the number of active requests, although a slow item can delay the rest of its batch.

A worker pool or semaphore can provide smoother scheduling when the application needs it. Choose limits based on the constrained resource and measure the real workload rather than assuming that more concurrent operations always finish faster.

Change the batch size to one and then three. Count the maximum outstanding operations in each version and explain the tradeoff when individual durations vary significantly.

A working example

using System;
using System.Linq;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;

var results = new List<int>();
foreach (var batch in new[] { 1, 2, 3, 4, 5 }.Chunk(2))
{
    var values = await Task.WhenAll(batch.Select(DoubleAsync));
    results.AddRange(values);
}
Console.WriteLine(string.Join(",", results));
async Task<int> DoubleAsync(int value) { await Task.Delay(1); return value * 2; }

Expected output

2,4,6,8,10
Try this
Bound the number of tasks you start, not merely the number you await later.