bhylu.com

Advertisements

C# Async Streams

Use IAsyncEnumerable to expose incremental work while passing cancellation through enumeration.

How it works

An async iterator can yield elements as asynchronous work completes. await foreach consumes it without requiring all results to be materialized first. The producer and consumer still need lifetime, error and cancellation contracts; incremental does not mean unbounded input is safe. EnumeratorCancellation helps connect the consumer’s token to the iterator parameter under this pattern. A stream can fail after already yielding some results, so a report should distinguish partial progress from complete success.

Run it locally

Use the .NET 10 SDK with C# 14 in a separate console lab. Create an empty project once with dotnet new console --framework net10.0, then replace Program.cs with this complete source and add any supporting files shown. Keep only one lesson entry point in the project. C# does not currently run in Bhylu’s browser playground. Apache, PHP and MySQL are not needed.

dotnet build -p:LangVersion=14.0 -p:Nullable=enable -p:TreatWarningsAsErrors=true
dotnet run --no-build

Use the SDK’s project workflow; the review compiles against its matching reference assemblies and executes the results without external NuGet packages. File-writing examples belong in a private empty lab directory.

Download the complete C# source as Program.cs.

Example

C# 14 · Program.cs

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Runtime.CompilerServices;

internal static class Program
{
    private static async System.Threading.Tasks.Task Main(string[] args)
    {
        await foreach (int count in CountsAsync(CancellationToken.None)) Console.WriteLine($"Count: {count}");
    }

    internal static async IAsyncEnumerable<int> CountsAsync([EnumeratorCancellation] CancellationToken token = default)
    {
        foreach (int count in new[] { 2, 4 })
        {
            token.ThrowIfCancellationRequested();
            await Task.Yield();
            yield return count;
        }
    }
}

Expected result

Count: 2
Count: 4

The fixture yields two small values in source order and checks its token on every iteration. It uses a controlled continuation boundary, not an external event stream. A file-backed or network-backed iterator must keep its owner alive until enumeration ends and release it even when the consumer stops early. Do not return an iterator whose required owner was disposed before consumption.

Consult Microsoft’s C# documentation for language and library contracts, including target-framework and version requirements.