1

I have a function that gets a sequence of items:

List<MyType> GetPage(int pageNr) {...} 

To get all available items you could do something like:

IEnumerable<MyType> FetchMyItems() { int pageNr = 0; var fetchedItems = GetPage(pageNr); while (fetchedItems.Any() { // there are items to return // yield return the fetched items: foreach (var fetchedItem in fetchedItems) yield return fetchedItems; // get the next page ++pageNr; fetchedItems = GetPage(pageNr) } } 

Using this method, my users won't have to fetch the items per page anymore, if they need only a few items only the first page will be fetched. More pages will be fetched automatically when they need more items. The disadvantage is of course that I sometimes fetch a few more items that won't be used.

I want an async version for this

So I have the following GetPage:

async Task<List<MyType>> GetPageAsync(int pageNr) {...} 

From what I understand from IEnumerable, is that it doesn't create the resulting sequence, it only creates the possibility to enumerate this sequence.

This enumerating is done explicitly using 'foreach', or implicitly using LINQ functions like 'ToList', 'ToArray', but also functions like 'FirstOrDefault', 'Any', Sum.

var fetchItemTasks = FetchMyItemsAsync(); foreach (var fetchItemTask in fetchedItemTasks) { MyType fetchedItem = await fetchItemTask; Process(fetchedItem); } 

Or if you do this low level:

var myitemsEnumerable = fetchMyItemsAsync(); // don't await yet, only create the possibility to enumerate var enumerator = myItemsEnumerable.GetEnumerator(); while (enumerator.MoveNext()) { // there is an item available in current: Task<MyType> task = enumerator.Current; return task; } 

Looking at this, It seems that the function should not return Task<IEnumerable<MyType>> but IEnumerable<Task<MyType>>, if you want to access an element you'll have to await for this.

Therefore, the function that returns the possibility to enumerate over awaitable items is not async for itself. You don't await for the enumerable, this enumerable can be created without accessing slow providers like a database. The items in the enumerable sequence are awaitable:

IEnumerable<Task<MyType>> FetchMyItems() { int pageNr = 0; Task<List<MyType>> fetchItemTask = GetPageAsync(pageNr); 

And now? If I await fetchItemTask, the items are already local nothing to await for anymore. If I fetchItemTask.Wait(), the function blocks.

3
  • the items are already local nothing to await for anymore What does that mean? Commented May 15, 2018 at 8:00
  • 4
    What you need is async enumerator, which does not yet has language support (here is a proposal: github.com/dotnet/csharplang/blob/master/proposals/…), but you can of course use this pattern without such support (without foreach, with more verbose syntax). Commented May 15, 2018 at 9:02
  • In the end I wanted to create extension functions like ToListAsync, ToArrayAsync, CountAsync, AnyAsync etc. But for them I need a basic IEnumerable. I'll read your proposal and check if creating my own EnumeratorAsync<TSource> would be the way to go Commented May 15, 2018 at 9:23

1 Answer 1

1

Your implementation of async/await pattern doesn't look correct.

GetPageAsync signature is fine:

async Task<List<MyType>> GetPageAsync(int pageNr) {...} 

Now, if you want to call this method asynchronously you have to use await keyword, which will suspend the executing method and return control to its caller until the GetPageAsync result is ready:

List<MyType> items = await GetPageAsync(pageNr); 

or

Task<List<MyType>> getPageTask = GetPageAsync(pageNr); // Some other stuff List<MyType> items = await getPageTask; 

But note that await keyword can only be used inside a method that is itself async.

Probably I don't realize the whole idea of your application async model, but in any case, I would recommend having a look at this MSDN article on asynchronous programming with async/await first.

Sign up to request clarification or add additional context in comments.

1 Comment

The problem is, that I don't know how many items my caller want. Does he only use FirstOrDefault? Will he Take(2000)? Hence I don't want to fetch more pages than needed. If he'll enumerate only the first few items, then fetching one page will be enough. Entity Framework has` ListAsync, FirstOrDefaultAsync` etc. To be able to create similar functions I need a base async function that returns the IEnumerable

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.