K4Code
Toggle theme
Back to Articles

Discovering the Power of IAsyncEnumerable in .NET

7 min read

If you've ever built a .NET app that juggles tons of data, you know the challenge of keeping things performant yet readable. While working on a recent project that involved fetching and processing large datasets, I discovered IAsyncEnumerable, which seriously leveled up my approach. In this post, I'll share my experience, explain what IAsyncEnumerable does, and show you why it's such a powerful tool for handling data as it arrives. Plus, I've got a simple example to make it all click.

The Challenge: Waiting on All that Data

I was building an app that needed to pull thousands of data items from an API and process them, like transforming, filtering, and then sending them to another system via REST. My first go-to was to fetch all the data into a list, then loop through it for processing. Simple enough, right? But there was a problem.

Fetching everything upfront was painfully slow. The app just sat there, waiting for the entire dataset to load into memory before it could do anything. It was sluggish and ate up memory, especially with big datasets. I knew there had to be a smarter way to make things faster and lighter.

Enter IAsyncEnumerable.

What is IAsyncEnumerable?

If you're familiar with IEnumerable or yield return, consider IAsyncEnumerable as their async-savvy sibling. Introduced in C# 8.0, IAsncEnumerable lets you work with a sequence of data that's produced asynchronously, you can process items as they come in, without waiting for the whole batch.

The beauty? Instead of loading everything into memory at once, IAsyncEnumerable streams data to you one at a time. You can start working on each item as soon as it's fetched, saving time and keeping memory usage low. And it's so clean to use--it hides a lot of the messy details of async data handling.

My "Wow" Moment with IAsyncEnumerable

I have used yield return before in a different setup to generate sequences lazily, but this use case showed me its real strength when paired with IAsyncEnumerable. The scenario was ideal: I needed to fetch data from an API in chunks (to avoid overloading the server and network bandwidth) and process it as it arrived. With IAsyncEnumerable, I could write code that was clear, straightforward, and super efficient.

The payoff? My app started processing data almost right away, instead of waiting for the data until the full fetch was done. The performance boost was obvious, especially the time it takes to complete the overall process of fetching and processing and the code was way easier to follow than some clunky alternatives I tried. It felt like I found a hidden gem in .NET (at least for me).

A Quick Example to See it Work

Let's make it concrete with a simple example. Suppose you're building an app that fetches items in the inventory from an API and processes them (like tagging the items, for now we just print them). Here's how IAsyncEnumerable can help.

csharp
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

public class Program
{
    public static async Task Main()
    {
        // Process inventory items as they're fetched from a paginated API
        await foreach (var item in FetchInventoryItemsFromApiAsync())
        {
            Console.WriteLine($"Processing item: {item.Name} (Qty: {item.Quantity})");
            await Task.Delay(100); // Simulate processing
        }
    }

    // Simulate fetching inventory items from a paginated REST API
    public static async IAsyncEnumerable<InventoryItem> FetchInventoryItemsFromApiAsync()
    {
        int page = 1;
        const int pageSize = 10;
        List<InventoryItem> items;

        do
        {
            items = await SimulateApiCallAsync(page, pageSize);
            foreach (var item in items)
            {
                yield return item;
            }
            page++;
        } while (items.Count == pageSize); // Continue while full pages are returned
    }

    // Simulate a REST API call to get a page of items
    private static Task<List<InventoryItem>> SimulateApiCallAsync(int page, int pageSize)
    {
        // Simulate a larger inventory
        var allItems = new List<InventoryItem>();
        for (int i = 1; i <= 25; i++) // 25 total items
        {
            allItems.Add(new InventoryItem
            {
                Id = i,
                Name = $"Item {i}",
                Quantity = 10 + i
            });
        }

        // Get the items for the requested page
        var pagedItems = allItems
            .Skip((page - 1) * pageSize)
            .Take(pageSize)
            .ToList();

        // Simulate API latency
        return Task.Delay(500).ContinueWith(_ => pagedItems);
    }

    public class InventoryItem
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int Quantity { get; set; }
    }
}

When to Reach for It

IAsyncEnumerable isn't the answer to everything, but it's a star in scenarios like:

  • Fetching data from APIs or databases in chunks.
  • Processing large datasets where loading it all into memory is a no-go.
  • Handling real-time data streams, like logs or the recent popular use case, streaming in Gen-AI applications.

💡 If your app doesn't deal with async data or huge datasets, a regular IEnumerable or List might do the trick.
But for async, high-volume data, IAsyncEnumerable is a lifesaver.

A Shoutout to yield return

I've got to give props to yield return.
I knew it was handy for lazy evaluation, but using it with IAsyncEnumerable showed me how much it can do.

It's like the quiet hero that makes generating async sequences feel effortless.
If you haven't dug into yield return yet, this is a great reason to experiment.

Wrapping It Up

Stumbling onto IAsyncEnumerable was a real eye-opener for me.
It took my .NET app from slow and memory-heavy to fast and lean, all while keeping my code clear and easy to read.

Whether you're pulling data from an API, crunching logs, or working with real-time streams, IAsyncEnumerable is worth a look.

-- Kalyan