Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Run enumeration of IAsyncEnumerable twice not possible?

Run enumeration of IAsyncEnumerable twice not possible?

Once CountAsync has been run, the await foreach won't enumerate any item. Why? It seems there is no Reset method on the AsyncEnumerator.

var count = await itemsToImport.CountAsync();

await foreach (var importEntity in itemsToImport)
{
    // won't run
}

Source of data:

private IAsyncEnumerable<TEntity> InternalImportFromStream(TextReader reader)
{
    var csvReader = new CsvReader(reader, Config);
        
    return csvReader.GetRecordsAsync<TEntity>();
}
like image 760
CleanCoder Avatar asked Mar 17 '20 15:03

CleanCoder


2 Answers

This has nothing to do with resetting an IAsyncEnumerator. This code attempts to generate a second IAsyncEnumerator, which, just like with IEnumerable.GetEnumerator() is only possible on some kinds of collections. If the Enumerable (async or not) is an abstraction over some sort of forward-only data structure, then GetEnumerator/GetAsyncEnumerator will fail.

And even when it doesn't fail, it's sometimes expensive. For instance it might run a database query or hit an remote API each time it's enumerated. This is why IEnumerable/IAsyncEnumerable make poor public return types from functions, as they fail to describe the capabilities of the returned collection, and almost the only thing you can do with the value is materialize it with .ToList/ToListAsync.

Eg, this works fine:

static async IAsyncEnumerable<int> Col()
{
    for (int i = 1; i <= 10; i++)
    {
        yield return i;
    }
}
static void Main(string[] args)
{
    Run().Wait();
}
static async Task Run()
{

    var col = Col();

    var count = await col.CountAsync();
    await foreach (var dataPoint in col)
    {
        Console.WriteLine(dataPoint);
    }
}
like image 90
David Browne - Microsoft Avatar answered Sep 27 '22 21:09

David Browne - Microsoft


It seems to be impossible to reset an IAsyncEnumerable by the interface itself, due to the fact, that there is no Reset method on the IAsyncEnumerator interface.

In this specific example the second enumeration won't work because the IAsyncEnumerable targets to a Stream. Once the stream has been read, the position cursor targets the stream's end. if you have control over the stream or a reference to it, (which I don't) you could set the position to 0 again and enumerate it again.

I tend to use ToListAsync and then get count out of its Count property and iterate the items synchronously because they are already loaded.

like image 23
CleanCoder Avatar answered Sep 27 '22 21:09

CleanCoder