Member-only story
Async Streams in C# 8+: From Task<List<T>> to IAsyncEnumerable and Channels
Collections are one of the fundamental pillars of every application. If you’ve ever written C# code, you’ve walked thousands of them with foreach, without even thinking about it. But what happens when the elements don't exist yet? When they arrive one page at a time from a database, from a supplier's API or from a queue? For years, the honest answer was: you wait for all of them, or you give up foreach.
public static async IAsyncEnumerable<string> ReadThisArticleAsync()
{
yield return "IAsyncEnumerable"; // the first part arrives now...
await Task.Delay(1000); // ...while the rest is still on its way
yield return "Channels";
}In this article, we’ll explore everything there is to know about async streams, from the workarounds before C# 8 to IAsyncEnumerable<T>, the LINQ built into .NET 10 and System.Threading.Channels, and take a look at what .NET 11 adds. The synchronous half of the story, IEnumerable<T> and yield return, belongs to the Iterator pattern, which will have its own article in this series: here we start with the asynchronous half.
Note: everything here is stable, from C# 8 to .NET 10 (LTS), and the code compiles with the .NET 10 SDK. Only the last section talks about .NET 11, still a release candidate, and says so.










