Is it possible to "await yield return DoSomethingAsync()"

jiangzhen picture jiangzhen · Feb 21, 2011 · Viewed 67.8k times · Source

Are regular iterator blocks (i.e. "yield return") incompatible with "async" and "await"?

This gives a good idea of what I'm trying to do:

async Task<IEnumerable<Foo>> Method(String [] Strs)
{
    // I want to compose the single result to the final result, so I use the SelectMany
    var finalResult = UrlStrings.SelectMany(link =>   //i have an Urlstring Collection 
                   await UrlString.DownLoadHtmlAsync()  //download single result; DownLoadHtmlAsync method will Download the url's html code 
              );
     return finalResult;
}

However, I get a compiler error citing "unable to load message string from resources".

Here is another attempt:

async Task<IEnumerable<Foo>> Method(String [] Strs)
{
    foreach(var str in strs)
    {
        yield return await DoSomethingAsync( str)
    }
}

But again, the compiler returns an error: "unable to load message string from resources".


Here is the real programming code in my project

This is very useful when I have an List Task,that task can be download HTML from a URL and I use the syntax "yield return await task", the result is I want IEnumerable<Foo>. I don't want write this code:

async Task<IEnumerable<String>> DownLoadAllURL(String [] Strs)
{
    List<Foo> htmls= new ...
    foreach(var str in strs)
    {
        var html= await DownLoadHtmlAsync( str)
        htmls.Add(item)
    }
    return htmls;
}

But it seems that I have to.

Thanks for any help.

Answer

Arne Claassen picture Arne Claassen · May 30, 2011

tl;dr Iterators as implemented with yield are a blocking construct, so as of right now await and yield are incompatible.

Long Because iterating over an IEnumerable is a blocking operation, calling a method marked as async will still execute it in a blocking manner, since it has to wait for that operation to finish.

async Task<IEnumerable<Foo>> Method(String [] Strs)
{
  foreach(var str in strs)
  {
    yield return await DoSomethingAsync( str)
  }
}  

The awaiting Method mixes meanings. Do you want to wait until the Task has an IEnumerable and then block on iterating over it? Or are you trying to await each value of the IEnumerable?

I assume the second is the desired behavior and in that case the existing Iterator semantics will not work. The IEnumerator<T> interface is basically

public interface IEnumerator<T>
  T Current;
  bool MoveNext();
}

I'm ignoring Reset() since it makes no sense for a sequence of asynchronous results. But what you would need is something like this:

public interface IAsyncEnumerator<T>
  T Current;
  Task<bool> MoveNext();
}

Of course, foreach also won't work with this and you'd have to iterate manually like this:

var moveNext = await asyncEnumerator.MoveNext();
while(moveNext) {

  // get the value that was fetche asynchronously
  var v = asyncEnumerator.Current;

  // do something with that value

  // suspend current execution context until next value arrives or we are done
  moveNext = await asyncEnumerator.MoveNext();
}