LINQ Выберите аналог для асинхронного метода - PullRequest
0 голосов
/ 24 февраля 2019

Допустим, у меня есть следующий пример кода:

private static async Task Main(string[] args)
{
    var result = Enumerable.Range(0, 3).Select(x => TestMethod(x)).ToArray();
    Console.ReadKey();
}

private static int TestMethod(int param)
{
    Console.WriteLine($"{param} before");
    Thread.Sleep(50);
    Console.WriteLine($"{param} after");
    return param;
}

TestMethod будет выполнен до 3 раз, поэтому я увижу 3 пары before и after:

0 before
0 after
1 before
1 after
2 before
2 after

Теперь мне нужно сделать TestMethod асинхронным:

private static async Task<int> TestMethod(int param)
{
    Console.WriteLine($"{param} before");
    await Task.Delay(50);
    Console.WriteLine($"{param} after");
    return param;
}

Как мне написать аналогичное выражение Select для этого асинхронного метода?Если я просто использую асинхронную лямбду Enumerable.Range(0, 3).Select(async x => await TestMethod(x)).ToArray();, она не будет работать, потому что она не будет ждать завершения, поэтому сначала будут вызываться before части:

0 before
1 before
2 before
2 after
0 after
1 after

Обратите внимание, что я нехочу выполнить все 3 вызова параллельно - мне нужно, чтобы они выполнялись один за другим, начиная следующий только после того, как предыдущий полностью завершил и вернул значение.

Ответы [ 5 ]

0 голосов
/ 27 февраля 2019

Похоже, что в настоящее время нет другого решения (до C # 8.0), кроме перечисления его вручную.Я сделал метод расширения для этого (возвращает список вместо массива, поскольку это проще):

public static async Task<List<T>> ToListAsync<T>(this IEnumerable<Task<T>> source)
{
    var result = new List<T>();
    foreach (var item in source)
    {
        result.Add(await item);
    }
    return result;
}
0 голосов
/ 26 февраля 2019

В C # 8 (следующая основная версия на момент написания) будет поддерживаться IAsyncEnumrable<T>, где вы можете написать асинхронный для каждого цикла:

await foreach (var item in GetItemsAsync())
    // Do something to each item in sequence

Я ожидаю, что будет Select метод расширения для проецирования, но если нет, то нетрудно написать свой собственный.Вы также сможете создавать IAsyncEnumrable<T> блоков итераторов с yield return.

0 голосов
/ 24 февраля 2019

Я регулярно сталкиваюсь с этим требованием и не знаю ни одного встроенного решения для его решения, как в C # 7.2.Обычно я просто возвращаюсь к использованию await для каждой асинхронной операции в пределах foreach, но вы можете использовать метод расширения:

public static class EnumerableExtensions
{
    public static async Task<IEnumerable<TResult>> SelectAsync<TSource, TResult>(
        this IEnumerable<TSource> source,
        Func<TSource, Task<TResult>> asyncSelector)
    {
        var results = new List<TResult>();
        foreach (var item in source)
            results.Add(await asyncSelector(item));
        return results;
    }
}

Затем вы вызовете await для SelectAsync:

static async Task Main(string[] args)
{
    var result = (await Enumerable.Range(0, 3).SelectAsync(x => TestMethod(x))).ToArray();
    Console.ReadKey();
}

Недостаток этого подхода в том, что SelectAsync стремится, а не ленится.C # 8 обещает ввести асинхронные потоки , что позволит этому снова быть ленивым.

0 голосов
/ 26 февраля 2019

Вы должны знать, что такое объект Enumerable.Перечислимый объект, не само перечисление.Это дает вам возможность получить объект Enumerator.Если у вас есть объект Enumerator, вы можете запросить первые элементы последовательности, а после того, как у вас есть элемент перечисления, вы можете запросить следующий.

Итак, создание объекта, который позволяет вамдля перечисления по последовательности не имеет ничего общего с самим перечислением.

Ваша функция Select возвращает только объект Enumerable, но не запускает перечисление.Перечисление начинается с ToArray.

. На самом низком уровне перечисление выполняется следующим образом:

IEnumerable<TSource> myEnumerable = ...
IEnumerator<TSource> enumerator = myEnumerable.GetEnumerator();
while (enumerator.MoveNext())
{
     // there is still an element in the enumeration
     TSource currentElement = enumerator.Current;
     Process(currentElement);
}

ToArray вызовет внутренние вызовы GetEnumerator и MoveNext.Поэтому, чтобы сделать оператор LINQ асинхронным, вам понадобится ToArrayAsync.

Код довольно прост.Я покажу вам ToListAsync как метод расширения, который немного проще сделать.

static class EnumerableAsyncExtensions
{
    public static async Task<List<TSource>> ToListAsync<TSource>(
       this IEnumerable<Task<TSource>> source)
    {
        List<TSource> result = new List<TSource>();
        var enumerator = source.GetEnumerator()
        while (enumerator.MoveNext())
        {
            // in baby steps. Feel free to do this in one step
            Task<TSource> current = enumerator.Current;
            TSource awaitedCurrent = await current;
            result.Add(awaitedCurrent);
        }
        return result;
    } 
}

Вам нужно будет создать его только один раз.Вы можете использовать его для любого ToListAsync, где вам придется ожидать каждого элемента:

 var result = Enumerable.Range(0, 3)
     .Select(i => TestMethod(i))
     .ToListAsync();

Обратите внимание, что возвращение Select равно IEnumerable<Task<int>>: объект, который позволяет перечислять последовательностьTask<int> объектов.В каждом цикле вы получаете Task<int>

0 голосов
/ 24 февраля 2019

Вы можете использовать примитивы синхронизации

    class Program
    {
        static async Task Main(string[] args)
        {
            var waitHandle = new AutoResetEvent(true);
            var result = Enumerable
                .Range(0, 3)
                .Select(async (int param) => {
                    waitHandle.WaitOne();
                    await TestMethod(param);
                    waitHandle.Set();
                }).ToArray();
            Console.ReadKey();
        }
        private static async Task<int> TestMethod(int param)
        {
            Console.WriteLine($"{param} before");
            await Task.Delay(50);
            Console.WriteLine($"{param} after");
            return param;
        }
    }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...