Asynchronous Streams in C# (IAsyncEnumerable)
Learn asynchronous streams in C# with IAsyncEnumerable to process data step by step using modern async iteration patterns.
In modern .NET applications, data often arrives in chunks and over time: streaming records from the network, log lines, sensor data…
IAsyncEnumerable<T> lets you consume such streams in an asynchronous and lazy manner.
On the producer side you use async + yield return, and on the consumer side you use await foreach.
What Is IAsyncEnumerable?
IAsyncEnumerable<T> allows you to produce and consume a sequence of data asynchronously.
Unlike synchronous IEnumerable<T>, each step may involve awaiting (e.g., due to I/O latency).
To consume, write await foreach; at each step the loop awaits until the next element is ready.
// Simple consumption
await foreach (var item in GetNumbersAsync())
{
Console.WriteLine(item);
}
Async Iterator: async + yield return
When writing an asynchronous producer method, return type is IAsyncEnumerable<T>;
inside the body you can await and produce items with yield return.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
static async IAsyncEnumerable<int> GetNumbersAsync()
{
for (int i = 1; i <= 5; i++)
{
await Task.Delay(500); // simulate I/O or time-consuming work
yield return i; // a new number every 500 ms
}
}
// Consumption
await foreach (var n in GetNumbersAsync())
{
Console.WriteLine($"Arrived: {n}");
}
Consuming with await foreach
await foreach pulls items from asynchronous streams without blocking the UI or the worker thread.
On each iteration it waits until data is ready; once it arrives, the loop resumes.
await foreach (var entry in ReadLogEntriesAsync())
{
// Process as soon as the entry arrives
Process(entry);
}
Error Handling and using
You can use standard try/catch when consuming async streams.
If an error occurs in the stream or during consumption, it surfaces while executing await foreach.
try
{
await foreach (var item in GetNumbersAsync())
Console.WriteLine(item);
}
catch (Exception ex)
{
Console.WriteLine("Error: " + ex.Message);
}
For asynchronous resources, use IAsyncDisposable and await using.
await using var source = await OpenResourceAsync();
await foreach (var x in source.StreamAsync())
{
// ...
}
Cancellation Support (CancellationToken)
To stop long-running streams, you can cancel consumption.
On the producer side, capture the token with [EnumeratorCancellation] and check it periodically.
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
static async IAsyncEnumerable<int> CountAsync(
int start, int count, [EnumeratorCancellation] CancellationToken ct = default)
{
for (int i = 0; i < count; i++)
{
ct.ThrowIfCancellationRequested();
await Task.Delay(300, ct);
yield return start + i;
}
}
// Consumption
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(1)); // cancel after 1s
await foreach (var n in CountAsync(10, 100, cts.Token))
{
Console.WriteLine(n);
}
Comparison: IEnumerable<Task<T>> vs IAsyncEnumerable<T>
- IEnumerable<Task<T>>: You typically produce all tasks up front and then consume; backpressure is weak.
- IAsyncEnumerable<T>: Items are produced on demand; memory usage and timing are more efficient.
Async Line-by-Line File Read (Wrapper Example)
The following example produces a stream that reads a file asynchronously, line by line.
Each line is emitted to the consumer via yield return as soon as it becomes available.
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
static async IAsyncEnumerable<string> ReadLinesAsync(string path)
{
using var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read,
bufferSize: 4096, useAsync: true);
using var sr = new StreamReader(fs);
while (!sr.EndOfStream)
{
var line = await sr.ReadLineAsync(); // when each line arrives
if (line is not null)
yield return line;
}
}
// Consumption
await foreach (var line in ReadLinesAsync("log.txt"))
{
Console.WriteLine(line);
}
Simple Filtering over a Stream (Pipeline)
Asynchronous streams can be chained to build a pipeline: produce → filter → transform → consume.
static async IAsyncEnumerable<int> FilterAsync(IAsyncEnumerable<int> source)
{
await foreach (var n in source)
{
if (n % 2 == 0) // pass even numbers
yield return n * 10; // transform
}
}
// Usage
await foreach (var x in FilterAsync(GetNumbersAsync()))
{
Console.WriteLine(x); // 20, 40, ...
}
UI Scenario: await foreach in WPF
In WPF, long-running streams won’t freeze the UI; you can update the interface as each item arrives.
If UI updates are required, marshal back to the Dispatcher on the UI thread.
// Example: read sensor values from a stream and write to the UI
await foreach (var v in ReadSensorAsync())
{
Dispatcher.Invoke(() => txtStatus.Text = $"Latest value: {v}");
}
Tips and Considerations
- Produce data in parts with
yield return; avoid loading large lists into memory all at once. - Provide cancellation (
CancellationToken); long streams should be stoppable by the user. - Handle errors at the right level with
try/catch; catch and log in the consumer when appropriate. - Use
Dispatcherfor UI updates; avoid accessing UI controls from non-UI threads.
TL;DR
- IAsyncEnumerable<T> is for asynchronous, lazy data streams.
- Producer:
async+yield return; Consumer:await foreach. - Use
[EnumeratorCancellation]andCancellationTokenfor cancellation. - IAsyncEnumerable<T> can be more efficient than IEnumerable<Task<T>> in memory and timing.
- If the UI needs updates, switch to the UI thread with
Dispatcher.
Related Articles
Asynchronous Programming Basics in C# (async/await)
Learn async and await in C# to build responsive applications with asynchronous tasks, non-blocking code, and practical examples.
Process and Thread Management in C#
Learn process and thread management in C# to control execution flow, system resources, and multithreaded applications.
Task Parallel Library (TPL) and Parallel Programming in C#
Learn Task Parallel Library and parallel programming in C# using Task, Parallel, and concurrency patterns with practical examples.