IAsyncEnumerable not working in C# 8.0 preview

前端 未结 2 1152
温柔的废话
温柔的废话 2021-02-20 11:40

I was playing around with C# 8.0 preview and can\'t get IAsyncEnumerable to work.

I tried the following

<         


        
2条回答
  •  深忆病人
    2021-02-20 12:30

    Regarding the bridging code needed to make Async enumerables work, I published a NuGet a couple of days ago that does just that: CSharp8Beta.AsyncIteratorPrerequisites.Unofficial

    Contrary to popular belief, the following code actually produces the expected results:

    private static async IAsyncEnumerable GetNumbersAsync()
    {
        var nums = Enumerable.Range(0, 10).ToArray();
        foreach (var num in nums)
        {
            await Task.Delay(100);
            yield return num;
        }
    }
    

    and that is because the IEnumerable is being materialized into an int array. What would actually terminate after two iterations is iterating over the IEnumerable itself like so:

    var nums = Enumerable.Range(0, 10); // no more .ToArray()
    foreach (var num in nums) {
    

    Still, while turning queries into materialized collections might seem like a clever trick, it isn't always the case that you would like to buffer the entire sequence (thus losing both memory and time).

    With performance in mind, what I found is that an almost zero allocating wrapper over the IEnumerable which would turn it into an IAsyncEnumerable plus using await foreach instead of just foreach would circumvent the issue.

    I have recently published a new version of the NuGet package which now includes an extension method called ToAsync() for IEnumerable in general, placed in System.Collections.Generic which does just that. The method's signature is:

    namespace System.Collections.Generic {
        public static class EnumerableExtensions {
            public static IAsyncEnumerable ToAsync(this IEnumerable @this)
    

    and upon adding the NuGet package to a .NET Core 3 project one could use it like so:

    using System.Collections.Generic;
    ...
    
    private static async IAsyncEnumerable GetNumbersAsync() {
        var nums = Enumerable.Range(0, 10);
        await foreach (var num in nums.ToAsync()) {
            await Task.Delay(100);
                yield return num;
            }
        }
    }
    

    Notice the two changes:

    • foreach becomes await foreach
    • nums becoms nums.ToAsync()

    The wrapper is as lightweight as possible and its implementation is based on the following classes (note that the using of ValueTask as enforced by the IAsyncEnumerable and IAsyncEnumerator allows for a constant number of Heap allocations per foreach):

    public static class EnumerableExtensions {
    
        public static IAsyncEnumerable ToAsync(this IEnumerable @this) => new EnumerableAdapter(@this);
    
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static IAsyncEnumerator ToAsync(this IEnumerator @this) => new EnumeratorAdapter(@this);
    
    
        private sealed class EnumerableAdapter : IAsyncEnumerable {
            private readonly IEnumerable target;
            public EnumerableAdapter(IEnumerable target) => this.target = target;
            public IAsyncEnumerator GetAsyncEnumerator() => this.target.GetEnumerator().ToAsync();
        }
    
        private sealed class EnumeratorAdapter : IAsyncEnumerator {
            private readonly IEnumerator enumerator;
            public EnumeratorAdapter(IEnumerator enumerator) => this.enumerator = enumerator;
    
            public ValueTask MoveNextAsync() => new ValueTask(this.enumerator.MoveNext());
            public T Current => this.enumerator.Current;
            public ValueTask DisposeAsync() {
                this.enumerator.Dispose();
                return new ValueTask();
            }
        } 
    }
    

    To sum it up:

    • To be able to write async generator methods ( async IAsyncEnumerable MyMethod() ...) and to consume async enumerables (await foreach (var x in ...) simply install the NuGet in your project.

    • In order to also circumvent the iteration premature stop, make sure you've got System.Collections.Generic in your using clauses, call .ToAsync() on your IEnumerable and turn your foreach into an await foreach.

提交回复
热议问题