IAsyncEnumerable In C# 8

Getting Setup With C# 8

If you aren’t sure if you are using C# 8, or you know you aren’t and want to know how to access these features. Read this quick guide on getting setup with .NET Core and C# 8.

Why?

With IOT becoming bigger and bigger, it makes sense for C# to add a way to iterate over an IEnumerable in an async way while using the yield keyword to get data as it comes in. For example retrieving data signals from an IOT box, we would want to be receiving data and processing it as it is retrieved, but not in a way that blocks CPU while we wait. This is where IAsyncEnumerable comes in!

But We Already Have Async Enumerable Right?

So a common trap to fall into might be that you want to use a return type of Task<IEnumerable<T>>  and make it async. Something like so :

static async Task Main(string[] args)
{
    foreach(var dataPoint in await FetchIOTData())
    {
        Console.WriteLine(dataPoint);
    }

    Console.ReadLine();
}

static async Task<IEnumerable<int>> FetchIOTData()
{
    List<int> dataPoints = new List<int>();
    for (int i = 1; i <= 10; i++)
    {
        await Task.Delay(1000);//Simulate waiting for data to come through. 
        dataPoints.Add(i);
    }

    return dataPoints;
}

So if we ran this application, what would be the result? Well nothing would appear for 10 seconds, and then we would see all of our datapoints all at once. It’s not going to be thread blocking, it’s still async, but we don’t get the data as soon as we receive it. It’s less IEnumerable and more like a List<T>.

What we really want, is to be able to use the yield  keyword, to return data as we receive it to be processed immediately.

Using Yield With IAsyncEnumerable

So knowing that we want to use yield , we can actually use the new interface in C# 8 called IAsyncEnumerable<T> . Here’s some code that does just that :

static async Task Main(string[] args)
{
    await foreach(var dataPoint in FetchIOTData())
    {
        Console.WriteLine(dataPoint);
    }

    Console.ReadLine();
}

static async IAsyncEnumerable<int> FetchIOTData()
{
    for (int i = 1; i <= 10; i++)
    {
        await Task.Delay(1000);//Simulate waiting for data to come through. 
        yield return i;
    }
}

So some pointers about this code :

  • Notice that we await our foreach loop itself rather than awaiting the FetchIOTData method call within the foreach loop.
  • We are returning a type of IAsyncEnumerable<T> and not IEnumerable<T>

Other than that, the code should be rather straight forward.

When we run this, instead of 10 seconds of nothing and then all data dumped on us, we get each piece of data as it comes. Ontop of this, the call is still not blocking.

Early Adopters Bonus

If you are using this in late 2018 or early 2019, you’re probably going to have fun trying to get all of this to compile. Common compile errors include :

Missing compiler required member 'System.Threading.Tasks.ManualResetValueTaskSourceLogic`1..ctor'
Missing compiler required member 'System.Runtime.CompilerServices.IStrongBox`1.Value'

As detailed in this Github issue coreclr repo, there is a mismatch between the compiler and the library.  You can get around this by copy and pasting this code block into your project until everything is back in sync again.

namespace System.Threading.Tasks
{
    using System.Runtime.CompilerServices;
    using System.Threading.Tasks.Sources;

    internal struct ManualResetValueTaskSourceLogic<TResult>
    {
        private ManualResetValueTaskSourceCore<TResult> _core;
        public ManualResetValueTaskSourceLogic(IStrongBox<ManualResetValueTaskSourceLogic<TResult>> parent) : this() { }
        public short Version => _core.Version;
        public TResult GetResult(short token) => _core.GetResult(token);
        public ValueTaskSourceStatus GetStatus(short token) => _core.GetStatus(token);
        public void OnCompleted(Action<object> continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) => _core.OnCompleted(continuation, state, token, flags);
        public void Reset() => _core.Reset();
        public void SetResult(TResult result) => _core.SetResult(result);
        public void SetException(Exception error) => _core.SetException(error);
    }
}

namespace System.Runtime.CompilerServices
{
    internal interface IStrongBox<T> { ref T Value { get; } }
}

There is even another issue where iteration will stop early (Wow!). It looks like it will be fixed in Preview 2. I couldn’t replicate this in my particular scenarios, but just know that it might happen.

Both of these issues maybe point to it being early days and not quite anything but proof of concept yet.

3 thoughts on “IAsyncEnumerable In C# 8”

  1. I want to make multiple HTTP requests in parallel and then yield return their results as they come in, regardless of order. But if I do:

    foreach(var call in calls) yield return await client.GetAsync(call);

    That ends up running each call one by one anyway, does it not? It halts the thread until the first result comes in, sends it to the iterator, then proceeds to the second call.
    If so, how would I make it actually run in parallel?

    Reply
    • For your particular issue yes. So if I understand you correctly, you want to do a bunch of GetAsync calls all at the same time, and as they come in return the results? For that you’re still going to need to use true multi threading like the Parallel library etc. This doesn’t solve that particular use case.

      What this instead solves is the fact that you can now use Yield to return them one by one full stop. As previously you were returning a Task<IEnumerable> which meant you could not use the yield keyword.

      Reply

Leave a Comment