ICYMI C# 8 New Features: Asynchronous Streams

This is part 8 in a series of articles.

In earlier versions of C# you could return an IEnumerable<T> from a method, for example to be consumed by a foreach loop.

The following example shows a method from a WPF app that returns 3 web pages as string content:

public static IEnumerable<string> LoadWebPages()
{
    using (var client = new HttpClient())
    {
        yield return client.GetStringAsync("http://dontcodetired.com/blog/post/ICYMI-C-8-New-Features-Switch-Expressions").Result;
        yield return client.GetStringAsync("http://dontcodetired.com/blog/post/ICYMI-C-8-New-Features-Write-Less-Code-with-Using-Declarations").Result;
        yield return client.GetStringAsync("http://dontcodetired.com/blog/post/ICYMI-C-8-New-Features-Write-Less-Code-with-Using-Declarations").Result;
    }
}

This method could be used in a click event handler in the WPF app (or via MVVM etc.):

private void NotAsync_Click(object sender, RoutedEventArgs e)
{
    foreach (var page in WebPageLoader.LoadWebPages())
    {
        var titleIndex = page.IndexOf("<title>");
        txt.Text += Environment.NewLine + $"Got page: {page.Substring(titleIndex, 110)}";
    }
}

When you run the app and click the button, the 3 web pages will be loaded and added to the content of the <TextBlock x:Name="txt"></TextBlock>

While loading the 3 web pages and looping through the foreach loop however, the app will be unresponsive until all 3 pages have been returned in the foreach loop.

C# 8 introduced the ability to use the IAsyncEnumerable<T> interface to iterate items asynchronously.

The “asynchronous streams” feature of C# 8 should not be confused with the streams in the System.IO namespace.

The LoadWebPages method can be re-written in C# 8 as:

public static async IAsyncEnumerable<string> LoadWebPagesAsync()
{
    using var client = new HttpClient();

    yield return await client.GetStringAsync("http://dontcodetired.com/blog/post/ICYMI-C-8-New-Features-Switch-Expressions");
    yield return await client.GetStringAsync("http://dontcodetired.com/blog/post/ICYMI-C-8-New-Features-Write-Less-Code-with-Using-Declarations");
    yield return await client.GetStringAsync("http://dontcodetired.com/blog/post/ICYMI-C-8-New-Features-Simplify-If-Statements-with-Property-Pattern-Matching");
}

And to consume this version:

private async void Async_Click(object sender, RoutedEventArgs e)
{
    await foreach (var page in WebPageLoader.LoadWebPagesAsync())
    {
        var titleIndex = page.IndexOf("<title>");
        txt.Text += Environment.NewLine + $"Got page: {page.Substring(titleIndex, 110)}";
    }
}

Now when the <Button Content="Async" x:Name="Async" Click="Async_Click"></Button> button is clicked, the 3 webpages will be enumerated in an async manner meaning that the UI will remain responsive.

The IAsyncEnumerable<T> interface also provides for cancellation: IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default);

If you want to fill in the gaps in your C# knowledge be sure to check out my C# Tips and Traps training course from Pluralsight – get started with a free trial.

SHARE:

Add comment

Loading