代码之家  ›  专栏  ›  技术社区  ›  Alen Alex

IAsyncEnumerable不在C 8.0预览中工作

  •  6
  • Alen Alex  · 技术社区  · 6 年前

    我在玩C 8.0预览版,无法 IAsyncEnumerable 工作。

    我尝试了以下方法

    public static async IAsyncEnumerable<int> Get()
    {
        for(int i=0; i<10; i++)
        {
            await Task.Delay(100);
            yield return i;
        }
    }
    

    我最终使用了一个名为 AsyncEnumerator ,但我得到以下错误:

    1. 错误CS1061' IAsyncEnumerable<int> '不包含' GetAwaiter '并且没有可访问的扩展方法' 等待者 '接受类型的第一个参数' IAsyncEnumerable<int> '可以找到(是否缺少using指令或程序集引用?)
    2. 错误CS1624的正文' Program.Get() '不能是迭代器块,因为' IAsyncEnumerable<int> '不是迭代器接口类型

    我这里缺什么?

    2 回复  |  直到 6 年前
        1
  •  9
  •   Panagiotis Kanavos    6 年前

    这是编译器中的一个错误,可以通过添加几行代码来修复。 found here :

    namespace System.Threading.Tasks
    {
        using System.Runtime.CompilerServices;
        using System.Threading.Tasks.Sources;
    
        internal struct ManualResetValueTaskSourceLogic<TResult>
        {
            private ManualResetValueTaskSourceCore<TResult> _core;
            public ManualResetValueTaskSourceLogic(IStrongBox<ManualResetValueTaskSourceLogic<TResult>> parent) : this() { }
            public short Version => _core.Version;
            public TResult GetResult(short token) => _core.GetResult(token);
            public ValueTaskSourceStatus GetStatus(short token) => _core.GetStatus(token);
            public void OnCompleted(Action<object> continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) => _core.OnCompleted(continuation, state, token, flags);
            public void Reset() => _core.Reset();
            public void SetResult(TResult result) => _core.SetResult(result);
            public void SetException(Exception error) => _core.SetException(error);
        }
    }
    
    namespace System.Runtime.CompilerServices
    {
        internal interface IStrongBox<T> { ref T Value { get; } }
    }
    

    正如Mads Torgersen在 Take C# 8 for a spin :

    但是,如果您尝试编译和运行它,您会得到大量的错误。这是因为我们有点搞砸了,而且.NET Core 3.0和Visual Studio 2019的预览没有完全对齐。具体地说,有一种实现类型,异步迭代器利用了与编译器预期不同的实现类型。

    您可以通过向项目中添加单独的源文件来修复此问题,其中包含 this bridging code . 重新编译,一切都会正常工作。

    更新

    看有另一个错误,当 Enumerable.Range() 在异步迭代器内使用。

    这个 GetNumbersAsync() 问题中的方法仅在两次迭代后结束:

    static async Task Main(string[] args)
    {
        await foreach (var num in GetNumbersAsync())
        {
            Console.WriteLine(num);
        }
    }
    
    private static async IAsyncEnumerable<int> GetNumbersAsync()
    {
        var nums = Enumerable.Range(0, 10);
        foreach (var num in nums)
        {
            await Task.Delay(100);
            yield return num;
        }
    }
    

    仅打印:

    0
    1
    

    这不会发生在数组或其他迭代器方法中:

    private static async IAsyncEnumerable<int> GetNumbersAsync()
    {
        foreach (var num in counter(10))
        {
            await Task.Delay(100);
            yield return num;
        }
    }
    
    private static IEnumerable<int> counter(int count)
    {
        for(int i=0;i<count;i++)
        {
            yield return i;
        }
    }
    

    这将打印预期的:

    0
    1
    2
    3
    4
    5
    6
    7
    8
    9
    

    更新2

    似乎这也是一个已知的错误: Async-Streams: iteration stops early on Core

        2
  •  0
  •   Eduard Dumitru    6 年前

    关于使异步可枚举工作所需的桥接代码,几天前我发布了一个nuget,它的作用是: CSharp8Beta.AsyncIteratorPrerequisites.Unofficial

    与人们普遍认为的相反,以下代码实际上产生了预期的结果:

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

    这是因为 IEnumerable<int> 被具体化为 int 数组。两次迭代后实际终止的是 IEnumerable<int> 本身就是这样:

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

    不过,尽管将查询转换为物化集合似乎是一个聪明的技巧,但并不总是希望缓冲整个序列(从而同时丢失内存和时间)。

    考虑到表演,我发现 几乎 零分配包装器 IEnumerable 它会变成一个 IAsyncEnumerable 加用 await foreach 而不仅仅是 foreach 会避开这个问题。

    我最近发布了一个新版本的nuget包,它现在包含一个名为 ToAsync<T>() 对于 IEnumerable<T> 一般来说,放在 System.Collections.Generic 就是这样。方法的签名是:

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

    在将nuget包添加到.NET核心3项目后,可以这样使用它:

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

    注意这两个变化:

    • 前额 变成 等待前程
    • nums 成为 nums.ToAsync()

    包装器尽可能轻,其实现基于以下类(请注意,使用 ValueTask<T> 执行 IAsyncEnumerable<T> IAsyncEnumerator<T> 允许为每个 前额 ):

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

    总结一下:

    • 能够编写异步生成器方法( async IAsyncEnumerable<int> MyMethod() ... )并使用异步可枚举项( await foreach (var x in ... )只需安装 NuGet 在你的项目中。

    • 为了避免迭代过早停止,请确保 system.collections.generic 在你的 using 条款,调用 .ToAsync() 对你 可枚举的 转动你的 前额 变成一个 等待前程 .

    推荐文章