How to use asynchronous streams in C# 8.0

Take advantage of the ability to create and consume data streams asynchronously in C# 8.0 to improve the performance of your applications

Asynchronous programming has been around for quite a while now. The introduction of the async and await keywords in .NET enabled us to write programs that could take advantage of asynchrony with ease. However, there hasn’t been any way to consume streams of data asynchronously until the arrival of IAsyncEnumerable<T> in C# 8.0.

IAsyncEnumerable<T> is similar to the IEnumerable<T> method used to iterate over a collection, except that IAsyncEnumerable<T> allows us to move through the collection asynchronously. In other words, IAsyncEnumerable<T> allows us to wait for the next element in the collection without blocking a thread.

In this article we’ll take a look at the challenges that IAsyncEnumerable<T> solves, with code examples wherever relevant. To work with the code examples provided in this article, you should have Visual Studio 2019 installed in your system. If you don’t already have a copy, you can download Visual Studio 2019 here

Create a console application project in Visual Studio 2019

First off, let’s create a .NET Core console application project in Visual Studio. Assuming Visual Studio 2019 is installed in your system, follow the steps outlined below to create a new .NET Core console application project in Visual Studio 2019.

  1. Launch the Visual Studio IDE.
  2. Click on “Create new project.”
  3. In the “Create new project” window, select “Console App (.NET Core)” from the list of templates displayed.
  4. Click Next. 
  5. In the “Configure your new project” window shown next, specify the name and location for the new project.
  6. Click Create. 

This will create a new .NET Core console application project in Visual Studio 2019. We’ll use this project to work with IAsyncEnumerable<T> and asynchronous streams in the subsequent sections of this article.

Specify the language version in Visual Studio 2019

To be able to work with C# 8.0 in Visual Studio, you must use a project that targets .NET Core, just as we’re doing. You will also need to change the language version of the language in use in your project. To do this, follow the steps outlined below:

  1. Right-click on the project. 
  2. Select “Properties” to invoke the properties window. 
  3. Click Build -> Advanced. 
  4. Click on the drop-down control for language version. 
  5. Select C# 8.0 as the language version.
  6. Click OK.

IAsyncDisposable, IAsyncEnumerable<T>, and IAsyncEnumerator<T> in C# 8.0

Asynchronous streams enable you to consume streams of data asynchronously. The interfaces IAsyncDisposable, IAsyncEnumerable<T>, and IAsyncEnumerator<T> have been introduced with the release of .NET Standard 2.1. These interfaces enable us to work with asynchronous streams. The following code snippet shows the code of these three interfaces.

public interface IAsyncDisposable
{
    ValueTask DisposeAsync();
}
public interface IAsyncEnumerable<out T>
{
    IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken
    token = default);
}
public interface IAsyncEnumerator<out T> : IAsyncDisposable
{
    ValueTask<bool> MoveNextAsync();
    T Current { get; }
}

Why use asynchronous streams? 

Imagine that you have a data access library that reads data from a data store and sends back all the results at one go. You can implement such a method easily. All you need to do is make some asynchronous calls to get the data from the underlying data store and then return all of the data at once.

This solution is fine as long as you don’t need to return data in pages; in that case, you might have to make several calls to return the data. The best solution in this regard is one in which you can send back the data to the caller as soon as the data is made available.

Here is exactly where asynchronous streams come to the rescue. If your method that returns data is synchronous, you could use the yield return statement with IEnumerable<T>. However, this solution would not scale because it would result in a blocking call.

The best solution is to use yield return in an asynchronous method that returns IAsyncEnumerable<T>. A method that returns an asynchronous stream returns an instance of IAsyncEnumerable<T> and contains one or more yield return statements.

Create an asynchronous stream in C# 8.0

The following code snippet illustrates an asynchronous method that returns Task<IEnumerable<T>>.

class Program
    {
        const int DELAY = 1000;
        const int MIN = 1;
        const int MAX = 10;
        static async Task Main(string[] args)
        {
            foreach (int number in await GetData())
            {
                Console.WriteLine(number);
            }
            Console.ReadLine();
        }
        static async Task<IEnumerable<int>> GetData()
        {
            List<int> integers = new List<int>();
            for (int i = MIN; i <= MAX; i++)
            {
                await Task.Delay(DELAY);
                integers.Add(i);
            }
            return integers;
        }
    }

When you execute this application, it will wait for 10 seconds and then display all of the numbers between 1 and 10 at the console window. Although the GetData method is asynchronous, this code will return all of these numbers at once, not one by one as they are generated.

This is where the yield keyword comes in. The yield keyword (introduced in C# 2.0) can perform stateful iteration and return each element of a collection one by one. You need not create a temporary collection to store data before it is returned.

The following code snippet shows how you can modify the GetData method to incorporate the yield keyword.

static async IAsyncEnumerable<int> GetData()
{
   for (int i = MIN; i < MAX; i++)
   {
      yield return i;
      await Task.Delay(DELAY);  
   }
}

Consume an asynchronous stream in C# 8.0 

When consuming an asynchronous stream, you need to specify the await keyword followed by the foreach keyword. You can call the method in the above example from the Main method as shown in the code snippet below.

static async Task Main(string[] args)
{
    await foreach (int number in GetData())
    {
        Console.WriteLine(number);
    }
  Console.ReadLine();
}

And here is the complete program for your reference.

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Test
{
    class Program
    {
        const int DELAY = 1000;
        const int MIN = 1;
        const int MAX = 10;
        static async Task Main(string[] args)
        {
            await foreach (int number in GetData())
            {
                Console.WriteLine(number);
            }
            Console.ReadLine();
        }
        static async IAsyncEnumerable<int> GetData()
        {
            for (int i = MIN; i < MAX; i++)
            {
                yield return i;
                await Task.Delay(DELAY);  
            }
        }
    }
}

The support for IAsyncEnumerable<T> (aka asynchronous streams) is one of the most important new features in C# 8.0. You can take advantage of asynchronous streams in your applications to make your code cleaner, more efficient, and high performant. To work with C# 8.0 compiler, you will need Visual Studio 2019 version 16.3 or the .NET Core 3.0 SDK.

Copyright © 2020 IDG Communications, Inc.