How would you correctly return a collection of objects asynchronously?

.net async-await c# entity-framework-core system.reactive

Question

I need to define methods in my core interface that return lists. My project heavily relies on the use of async/await so I need to define my core references/interfaces as asynchronous as possible. I also use EF7 for my data-access layer. I currently use IAsyncEnumerable everywhere.

I am currently deciding whether to keep using IAsyncEnumerable or to revert back to using Task<IEnumerable<T>>. IAsyncEnumerable seems promising at this point. EF7 is using it as well. The trouble is, I don't know and can't figure out how to use it. There is almost nothing on the website that tells anyone how to use Ix.Net. There's a ToAsyncEnumerable extension that I can use on IEnumerable objects but this wouldn't do anything asynchronously (or does it??). Another drawback is that given the below signature:

IAsyncEnumerable GetPersons();

Because this isn't a function that returns Task, I can't use async/await inside the function block.

On the other hand, my gut is telling me that I should stick with using Task<IEnumerable<T>>. This of course has it's problems as well. EF does not have an extension method that returns this type. It has a ToArrayAsync and ToListAsync extension method but this of course requires you to call await inside the method because Task<T> isn't covariant. This potentially is a problem because this creates an extra operation which could be avoided if I simply return the Task object.

My questions is: Should I keep using IAsyncEnumerable (preferred) or should I change everything back to Task<IEnumerable<T>> (not preferred)? I'm open to other suggestions as well.

Popular Answer

I would go with IAsyncEnumerable. It allows you to keep your operations both asynchronous and lazy.

Without it you need to return Task<IEnumerble> which means you're loading all the results into memory. This in many cases meaning querying and holding more memory than needed.

The classic case is having a query that the user calls Any on. If it's Task<IEnumerable> it will load all the results into memory first, and if it's IAsyncEnumerable loading one result will be enough.

Also relevant is that with Task<IEnumerable> you need to hold the entire result set in memory at the same time while with IAsyncEnumerable you can "stream" the results a few at a time.

Also, that's the direction the ecosystem is heading. It was added by reactive extension, by a new library suggested by Stephen Toub just this week and will probably be supported in the next version of C# natively.



Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why