Executing query until there is nothing more left I often query a database to get a batch of items to procees. I do this as long as the query returns some items. I use this pattern quite a lot so I thought I create a small helper so that I don't have to implement this logic again and again.
It's a small class that executes the query until there is nothing more left:
public static class Unfold
{
    public static async Task ForEachAsync&lt;T&gt;
    (
        Func&lt;CancellationToken, Task&lt;IList&lt;T&gt;&gt;&gt; query, 
        Func&lt;IList&lt;T&gt;, CancellationToken, Task&gt; body, 
        CancellationToken cancellationToken
    )
    {
        while (true)
        {
            var result = await query(cancellationToken);
            if (result.Any())
            {
                await body(result, cancellationToken);
            }
            else
            {
                break;
            }
        }
    }
}

The reason why I implemented it exactly this way is:

all my queries are async
they must always return IList&lt;T&gt; (if they return a collection of course)
I always process a batch at a time that I then mark as processed


Example
The typical use-case is like this:

get a batch of items from a repository
process this batch
repeat until the batch is empty


async Task Main()
{
    var numbers = new NumberRepository();
    await Unfold.ForEachAsync
    (
        query: async token =&gt; await numbers.GetNumbersAsync(token), 
        body: ProcessBatch, 
        CancellationToken.None
    );
}

A test repository:
public class NumberRepository
{
    private readonly IList&lt;IList&lt;int&gt;&gt; _numbers = new[] { new[] { 1, 2 }, new[] { 3, 4 }, new[] { 5 }, new int[0] };
    private int _batchIndex;

    public Task&lt;IList&lt;int&gt;&gt; GetNumbersAsync(CancellationToken cancellationToken) =&gt; Task.FromResult(_numbers[_batchIndex++]);
}

and the processing method:
private Task ProcessBatch&lt;T&gt;(T item, CancellationToken cancellationToken)
{
    item.Dump();
    return Task.CompletedTask;
}


What do you say? Is this a good or a bad solution? Is there anything missing (but null-checks)?
Sorry, but you definitely need a null check here:


  if (result.Any())


Else there is not much to comment.

About the usage:

I don't understand, why you create a lambda for the query argument:


 async Task Main()
{
    var numbers = new NumberRepository();
    await Unfold.ForEachAsync
    (
        query: async token =&gt; await numbers.GetNumbersAsync(token), 
        body: ProcessBatch, 
        CancellationToken.None
    );
}



Why not just:

async Task Main()
{
  var numbers = new NumberRepository();
  await Unfold.ForEachAsync
  (
      query: numbers.GetNumbersAsync,
      body: ProcessBatch,
      CancellationToken.None
  );
}


numbers.GetNumbersAsync is awaitable already?