C# tutorials > Asynchronous Programming > Async and Await > How do you use `async` streams (`IAsyncEnumerable<T>`)?
How do you use `async` streams (`IAsyncEnumerable<T>`)?
Understanding Asynchronous Streams in C#
This tutorial explores how to use IAsyncEnumerable<T>
in C# for asynchronous stream processing. Asynchronous streams allow you to efficiently handle sequences of data that are produced asynchronously, offering improved performance and responsiveness in applications that deal with I/O-bound or computationally intensive operations.
Basic Example: Generating an Asynchronous Stream
This example demonstrates a simple asynchronous stream generator. The GenerateNumbersAsync
method produces a sequence of integers asynchronously. Key elements include:
IAsyncEnumerable<int>
: The return type indicates that the method returns an asynchronous stream of integers.async
: The async
keyword enables the use of await
within the method.await Task.Delay(100)
: Simulates an asynchronous operation (e.g., fetching data from a database or network).yield return i
: Yields each integer in the sequence asynchronously.await foreach
: The Main
method uses await foreach
to iterate through the asynchronous stream, processing each element as it becomes available.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
public class AsyncStreamExample
{
public static async IAsyncEnumerable<int> GenerateNumbersAsync(int count)
{
for (int i = 0; i < count; i++)
{
await Task.Delay(100); // Simulate an asynchronous operation
yield return i;
}
}
public static async Task Main(string[] args)
{
await foreach (var number in GenerateNumbersAsync(5))
{
Console.WriteLine(number);
}
}
}
Concepts Behind the Snippet
Asynchronous streams (IAsyncEnumerable<T>
) are built upon the principles of asynchronous programming and iterators.
IAsyncEnumerable<T>
: Represents an asynchronous sequence of values that can be iterated over using await foreach
.IAsyncEnumerator<T>
: An interface that provides the mechanism to iterate over an IAsyncEnumerable<T>
. It has a MoveNextAsync()
method and a Current
property, similar to IEnumerator<T>
but designed for asynchronous operations.
Real-Life Use Case Section
Asynchronous streams are valuable in scenarios where you need to process data in chunks and avoid blocking the UI thread. Examples include:
Best Practices
CancellationToken
to allow users to interrupt long-running operations.await using
.
Interview Tip
When discussing asynchronous streams in interviews, emphasize their role in improving application responsiveness and scalability. Be prepared to discuss real-world scenarios where asynchronous streams can be effectively used and how they differ from traditional synchronous iteration. Mention IAsyncDisposable
and its importance.
When to use them
Use IAsyncEnumerable<T>
when:
Memory Footprint
Asynchronous streams generally have a lower memory footprint than loading the entire sequence into memory at once, because they process data in chunks. However, the memory usage still depends on the size of each chunk and the overall length of the stream.
Alternatives
Alternatives to IAsyncEnumerable<T>
include:
IEnumerable<T>
): Suitable for synchronous operations where the entire sequence can be loaded into memory.
Pros
await foreach
.
Cons
Using Cancellation Tokens
This example demonstrates the use of It's also important to use the CancellationToken
to allow cancellation of an asynchronous stream. CancellationToken
is passed to the asynchronous stream generator, and the operation checks cancellationToken.IsCancellationRequested
at regular intervals. If cancellation is requested, the method exits gracefully using yield break
.CancellationToken
when awaiting tasks, like Task.Delay(100, cancellationToken)
, to ensure they are cancellable.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
public class AsyncStreamCancellationExample
{
public static async IAsyncEnumerable<int> GenerateNumbersAsync(int count, CancellationToken cancellationToken)
{
for (int i = 0; i < count; i++)
{
if (cancellationToken.IsCancellationRequested)
{
Console.WriteLine("Cancellation Requested");
yield break;
}
await Task.Delay(100, cancellationToken); // Simulate an asynchronous operation
yield return i;
}
}
public static async Task Main(string[] args)
{
var cancellationTokenSource = new CancellationTokenSource();
// Cancel after 300ms
Task.Delay(300).ContinueWith(_ => cancellationTokenSource.Cancel());
try
{
await foreach (var number in GenerateNumbersAsync(5, cancellationTokenSource.Token))
{
Console.WriteLine(number);
}
}
catch (TaskCanceledException)
{
Console.WriteLine("Task was cancelled.");
}
finally
{
cancellationTokenSource.Dispose();
}
}
}
FAQ
-
What is the difference between `IEnumerable
` and `IAsyncEnumerable `?
`IEnumerable` is for synchronous iteration, where all data is available in memory. `IAsyncEnumerable ` is for asynchronous iteration, where data is produced or consumed asynchronously, potentially involving I/O or other long-running operations. `IAsyncEnumerable ` requires the use of `await foreach` to consume the stream. -
How do I handle exceptions in `IAsyncEnumerable
`?
Wrap the `await foreach` loop in a `try-catch` block to catch exceptions that occur during asynchronous iteration. Inside the stream generator, handle exceptions appropriately and potentially yield a special value or re-throw the exception based on the requirements. It is important to handle all exceptions possible, because unhandled exceptions will crash the application. Example try-catch block: csharp try { await foreach (var item in GetAsyncEnumerable()) { // Process item } } catch (Exception ex) { // Handle the exception Console.WriteLine($"An error occurred: {ex.Message}"); } -
Why do I need to use `await using` when working with async streams?
`await using` ensures that asynchronous resources used by the stream, such as file streams or database connections, are properly disposed of when the stream is no longer needed. This prevents resource leaks and ensures the stability of your application. `await using` automatically calls the `DisposeAsync()` method of the resource.