SyntaxStudy
Sign Up
C# Async Streams and Channels
C# Beginner 1 min read

Async Streams and Channels

Async streams (`IAsyncEnumerable`) combine the benefits of `IEnumerable` with `async/await`. An async iterator method decorated with `async` and returning `IAsyncEnumerable` uses `yield return` to produce values asynchronously. The consumer iterates with `await foreach`. This is ideal for reading paginated APIs, database cursors, and file streams without buffering everything in memory. `System.Threading.Channels` provides a high-performance producer-consumer pipeline primitive. A `Channel` has a writer end and a reader end. Writers `TryWrite` or `await WriteAsync`; readers `await ReadAsync` or `await foreach` over `Reader.ReadAllAsync()`. Channels are the modern alternative to `BlockingCollection` and `ConcurrentQueue` when producers and consumers run on different threads or tasks. The `ConfigureAwait(false)` call on an awaitable instructs the continuation not to capture the current synchronisation context. This is a performance best practice in library code to avoid unnecessary context switches back to the UI or ASP.NET request context.
Example
// Async streams with IAsyncEnumerable<T> and Channels

using System.Threading.Channels;

// --- Async stream producer ---
static async IAsyncEnumerable<int> GenerateNumbersAsync(
    int count,
    [System.Runtime.CompilerServices.EnumeratorCancellation]
    CancellationToken ct = default)
{
    for (int i = 0; i < count; i++)
    {
        await Task.Delay(50, ct).ConfigureAwait(false);
        yield return i * i;
    }
}

// Consumer using await foreach
await foreach (int square in GenerateNumbersAsync(8).WithCancellation(CancellationToken.None))
    Console.Write($"{square} ");
Console.WriteLine();

// --- Channels: bounded producer-consumer ---
var channel = Channel.CreateBounded<string>(capacity: 5);

async Task ProduceAsync(ChannelWriter<string> writer)
{
    for (int i = 0; i < 10; i++)
    {
        await writer.WriteAsync($"message-{i}");
        await Task.Delay(20);
    }
    writer.Complete();
}

async Task ConsumeAsync(ChannelReader<string> reader)
{
    await foreach (string msg in reader.ReadAllAsync())
        Console.WriteLine($"[Consumer] received: {msg}");
}

await Task.WhenAll(
    ProduceAsync(channel.Writer),
    ConsumeAsync(channel.Reader));