Search code examples
c#ado.netasync-awaitgeneratoryield-return

How can I make `await …` work with `yield return` (i.e. inside an iterator method)?


I have existing code that looks similar to:

IEnumerable<SomeClass> GetStuff()
{
    using (SqlConnection conn = new SqlConnection(connectionString))
    using (SqlCommand cmd = new SqlCommand(sql, conn)
    {
        conn.Open();
        SqlDataReader reader = cmd.ExecuteReader();
        while (reader.Read())
        {
            SomeClass someClass = f(reader); // create instance based on returned row
            yield return someClass;
        }
    } 
}

It seems I could benefit by using reader.ReadAsync(). However, if I just modify the one line:

        while (await reader.ReadAsync())

the compiler informs me that await can only be used in methods marked with async, and suggests I modify the method signature to be:

async Task<IEnumerable<SomeClass>> GetStuff()

However, doing that makes GetStuff() unusable because:

The body of GetStuff() cannot be an iterator block because Task<IEnumerable<SomeClass>> is not an iterator interface type.

I'm sure I am missing a key concept with the async programming model.

Questions:

  • Can I use ReadAsync() in my iterator? How?
  • How can I think about the async paradigm differently so that I understand how it works in this type of situation?

Solution

  • As of C# 8, this can be accomplished with IAsyncEnumerable

    Modified code:

    async IAsyncEnumerable<SomeClass> GetStuff()
    {
        using (SqlConnection conn = new SqlConnection(connectionString))
        using (SqlCommand cmd = new SqlCommand(sql, conn)
        {
            conn.Open();
            SqlDataReader reader = cmd.ExecuteReader();
            while (reader.Read())
            {
                SomeClass someClass = f(reader); // create instance based on returned row
                yield return someClass;
            }
        } 
    }
    

    Consume it like this:

    await foreach (var stuff in GetStuff())
        ...