IAsyncEnumerable не работает в C # 8.0 превью - PullRequest
0 голосов
/ 06 декабря 2018

Я играл с предварительным просмотром C # 8.0 и не могу заставить IAsyncEnumerable работать.

Я попробовал следующее

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

В итоге я использовал пакет Nuget с именем AsyncEnumerator, но я получаю следующую ошибку:

  1. Ошибка CS1061 'IAsyncEnumerable<int>' не содержит определения для 'GetAwaiter' и нет доступного метода расширения 'GetAwaiter', принимающегоможет быть найден первый аргумент типа 'IAsyncEnumerable<int>' (отсутствует директива using или ссылка на сборку?)
  2. Ошибка CS1624 Тело 'Program.Get()' не может быть блоком итератора, поскольку 'IAsyncEnumerable<int> 'не является типом интерфейса итератора

Что мне здесь не хватает?

Ответы [ 2 ]

0 голосов
/ 08 декабря 2018

Относительно кода моста, необходимого для работы перечислимых Async, я опубликовал 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 Core 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;
        }
    }
}

Обратите внимание на два изменения:

  • foreach становится await foreach
  • nums becoms nums.ToAsync()

Обертка настолько легка, насколько это возможно, и ее реализация основана наследующие классы (обратите внимание, что использование ValueTask<T> в соответствии с IAsyncEnumerable<T> и IAsyncEnumerator<T> допускает постоянное количество выделений кучи на foreach):

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() на вашем IEnumerable и включите foreach в await foreach.

0 голосов
/ 06 декабря 2018

Это ошибка в компиляторе, которую можно исправить, добавив несколько строк кода , найденных здесь :

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; } }
}

Как объясняет Мадс Торгерсен в Take C #8 за спин :

Но если вы попытаетесь скомпилировать и запустить его, вы получите смущающее количество ошибок.Это потому, что мы немного запутались и не смогли полностью согласовать превью .NET Core 3.0 и Visual Studio 2019.В частности, есть тип реализации, который используют асинхронные итераторы, который отличается от того, что ожидает компилятор.

Это можно исправить, добавив отдельный исходный файл в ваш проект, содержащий этот код моста .Снова скомпилируйте, и все должно работать нормально.

Обновление

Похоже, есть другая ошибка, когда 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: итерация рано останавливается на Core

...