Answered can I "yield return await doAsync()"

  • Friday, February 18, 2011 8:44 AM
     
      Has Code
    how i can :
    async Task<IEnumerable<string>> Foo(List<string>list)
    {
    foreach(var item in list)
    {
    yield return await DosomethingAsync(item);
    }
    }
    thanks for any help. 

Answers

  • Friday, February 18, 2011 8:23 PM
     
     Answered

    You are not NOT permitted to have an Async Iterator Function in VB.   It can be an iterator Function or it can be an Async Function both not both.

    This same limitation exist in C# preventing await and yield in the same method, although the error message may not be as nice  because of language syntax in C#.

     

     

  • Sunday, February 20, 2011 4:26 PM
    Moderator
     
     Answered

    Iterators and asynchronous methods are in conflict as to their semantics.  With an iterator, when you yield a value, that value is immediately visible to the caller, e.g. they call MoveNext and the MoveNext call returns to them at the "yield return" point.  However, your async method returns a Task<>, not an enumerable, so there's no returning partial values to the caller.   To do what you're looking to do, the compiler would need support for asynchronous enumeration and some type to represent that (e.g. IAsyncEnumerable), which the CTP does not have. Since you're not building an iterator, you can easily express the logic you want by building up a collection and returning it, e.g.

    async Task<IEnumerable<string>> Foo(List<string>list)
    {
        var results = new List<string>();
        foreach(var item in list)
        {
            results.Add(await DoSomethingAsync(item));
        }
        return result;
    }

     

  • Monday, February 21, 2011 3:38 PM
    Moderator
     
     Answered

    Hi-

    My point was that they're in conflict the way you currently define them: if the caller of your method expects a Task<IEnumerable<string>>, such that there's no data available to them until the task completes and then all of the data is available, then the asynchronous method is not a stream generator and does not lazily generate one element at a time, with each MoveNext call going back into the function to produce the next element.  At that point, you might as well just build up an in-memory list of the results, because that's all the compiler would be doing under the covers if yield return was supported in a method that returned Task<IEnumerable<string>>.  What you're asking for is a new interface, something like IAsyncEnumerable<T>, and compiler support for generating it; that functionality is certainly tenable for the compiler to support in the future, but it's not included in this release.

All Replies

  • Friday, February 18, 2011 8:23 PM
     
     Answered

    You are not NOT permitted to have an Async Iterator Function in VB.   It can be an iterator Function or it can be an Async Function both not both.

    This same limitation exist in C# preventing await and yield in the same method, although the error message may not be as nice  because of language syntax in C#.

     

     

  • Saturday, February 19, 2011 1:08 AM
     
     
    but,i think "yield return await..." is a nature way in this logic.Is not it? So i am comfuse why dose not support it .
  • Sunday, February 20, 2011 4:26 PM
    Moderator
     
     Answered

    Iterators and asynchronous methods are in conflict as to their semantics.  With an iterator, when you yield a value, that value is immediately visible to the caller, e.g. they call MoveNext and the MoveNext call returns to them at the "yield return" point.  However, your async method returns a Task<>, not an enumerable, so there's no returning partial values to the caller.   To do what you're looking to do, the compiler would need support for asynchronous enumeration and some type to represent that (e.g. IAsyncEnumerable), which the CTP does not have. Since you're not building an iterator, you can easily express the logic you want by building up a collection and returning it, e.g.

    async Task<IEnumerable<string>> Foo(List<string>list)
    {
        var results = new List<string>();
        foreach(var item in list)
        {
            results.Add(await DoSomethingAsync(item));
        }
        return result;
    }

     

  • Monday, February 21, 2011 5:57 AM
     
     

    thanks Toub

    i think Iterators and asynchronous methods are not in conflict as to their semantics.

    yield return await .. //e.g. await a string

    i meen that  yield a value ,that value is not immediately visible to the caller , just like:

    first excute on ‘yield return await DoSomethingAsync()“ // mark ONE, no blocking ,go on excute

    and second excute on ” yield return await DoSomethingAsync()“ mark TWO 

    the  return result is :

    if ONE first completed  the result is: one two

    is TWO first completed the result is: two one 

    the result inner item order is not important.

  • Monday, February 21, 2011 3:38 PM
    Moderator
     
     Answered

    Hi-

    My point was that they're in conflict the way you currently define them: if the caller of your method expects a Task<IEnumerable<string>>, such that there's no data available to them until the task completes and then all of the data is available, then the asynchronous method is not a stream generator and does not lazily generate one element at a time, with each MoveNext call going back into the function to produce the next element.  At that point, you might as well just build up an in-memory list of the results, because that's all the compiler would be doing under the covers if yield return was supported in a method that returned Task<IEnumerable<string>>.  What you're asking for is a new interface, something like IAsyncEnumerable<T>, and compiler support for generating it; that functionality is certainly tenable for the compiler to support in the future, but it's not included in this release.

  • Tuesday, February 22, 2011 4:19 PM
     
     

    thank you

    now , i understood your point.

  • Wednesday, August 31, 2011 6:52 PM
     
      Has Code

    Sorry to resurrect this old thread; just some thoughts/ideas around this - in that I don't think a new interface is required.

    Starting off with the simplest example:

            static async void UsesAsyncEnumerable()
            {
                foreach (var value in AsyncEnumerable(m_values))
                {
                    DoSomethingWithResult(value);
                }
            }

            static async IEnumerable<Task<int>> AsyncEnumerable(IEnumerable<string> values)
            {
                foreach (var value in values)
                {
                    yield return await DoSomeHeavyLifting(value);
                }
            }

    The 'backend' code for this would be (that compiles today, and is hence a workaround that works today):

            static async void UsesAsyncEnumerable()
            {
                foreach (var valueTask in AsyncEnumerable(m_values))
                {
                    var value = await valueTask;
                    DoSomethingWithResult(value);
                }
            }

            static IEnumerable<Task<int>> AsyncEnumerable(IEnumerable<string> values)
            {
                foreach (var value in values)
                {
                    yield return DoSomeHeavyLifting(value);
                }
            }

    Now we can expand the problem to await before the loop:

            static async void UsesAsyncEnumerable()
            {
                foreach (var value in AsyncEnumerable(m_values))
                {
                    DoSomethingWithResult(value);
                }
            }

            static async IEnumerable<Task<int>> AsyncEnumerable(IEnumerable<string> values)
            {
                await SetUpSomeStuff();
                foreach (var value in values)
                {
                    yield return await DoSomeHeavyLifting(value);
                }
            }

     This would get normalised to:

            static async void UsesAsyncEnumerable()
            {
                foreach (var value in (await AsyncEnumerable(m_values)))
                {
                    DoSomethingWithResult(value);
                }
            }

            static async Task<IEnumerable<Task<int>>> AsyncEnumerable(IEnumerable<string> values)
            {
                await SetUpSomeStuff();
                return AsyncEnumerableImpl(values);
            }

            static IEnumerable<Task<int>> AsyncEnumerableImpl(IEnumerable<string> values)
            {
                foreach (var value in values)
                {
                    yield return DoSomeHeavyLifting(value);
                }
            }

    There are some restrictions, but a lot of the cases where this would be valuable are catered for. Here is a program that actually executes and demonstrates the expected behavior:

     

            static void Main(string[] args)
            {
                Task.WaitAll(Execute(new int[] { 1, 2, 3, 4 }));
                Console.ReadLine();
            }

            static async Task Execute(IEnumerable<int> values)
            {
                foreach (var task in (await AsyncEnumerable(values)))
                {
                    var value = await task;
                    Console.WriteLine("Got value {0}", value);
                }
            }

            static async Task<IEnumerable<Task<string>>> AsyncEnumerable(IEnumerable<int> values)
            {
                await WriteLine("Starting...");
                return AsyncEnumerableImpl(values);
            }

            static IEnumerable<Task<string>> AsyncEnumerableImpl(IEnumerable<int> values)
            {
                foreach(var item in values)
                {
                    Console.WriteLine("Start of loop body {0}", item);
                    yield return WriteLine(item.ToString());
                    Console.WriteLine("End of loop body {0}", item);
                }
            }

            static async Task<string> WriteLine(string value)
            {
                Console.WriteLine(value);
                await TaskEx.Delay(1000); // Such an expensive call.
                return value;
            }

    NB: Notice I haven't yet figured out 'footer' awaits. This is probably where the new interface would come in (await enumerator.Dispose()); or, yielding a ImplTask<TResult> that the iterator logic knows to ignore (i.e. the body of the loop is skipped).
    • Edited by jcdickinson Wednesday, August 31, 2011 7:06 PM Note about footers.
    •