Подтвердить что ты не робот

Вызов метода async в IEnumerable.Select

У меня есть следующий код, который преобразует элементы между типами R и L с помощью метода async:

class MyClass<R,L> {

    public async Task<bool> MyMethodAsync(List<R> remoteItems) {
        ...

        List<L> mappedItems = new List<L>();
        foreach (var remoteItem  in remoteItems )
        {
            mappedItems.Add(await MapToLocalObject(remoteItem));
        }

        //Do stuff with mapped items

        ...
    }

    private async Task<L> MapToLocalObject(R remoteObject);
}

Можно ли написать с помощью вызова IEnumerable.Select(или аналогичного) для сокращения строк кода? Я пробовал это:

class MyClass<R,L> {

    public async Task<bool> MyMethodAsync(List<R> remoteItems) {
        ...

        List<L> mappedItems = remoteItems.Select<R, L>(async r => await MapToLocalObject(r)).ToList<L>();

        //Do stuff with mapped items

        ...
    }
}

Но я получаю ошибку:

"Невозможно преобразовать выражение async lambda в тип делегирования 'System.Func<R,int,L>'. Асинхронное лямбда-выражение может возвращать void, Task или Task<T>, ни одна из которых не может быть конвертирована в 'System.Func<R,int,L>'".

Я считаю, что я что-то пропустил по ключевым словам async/wait, но я не могу понять, что. Кто-нибудь знает, как я могу изменить свой код, чтобы он работал?

4b9b3361

Ответ 1

Вы можете это решить, рассматривая типы в игре. Например, MapToLocalObject - если рассматривать как асинхронную функцию - отображает от R до L. Но если вы рассматриваете его как синхронную функцию, он отображает от R до Task<L>.

Task - это "будущее", поэтому Task<L> можно рассматривать как тип, который в какой-то момент в будущем будет создавать L.

Таким образом, вы можете легко преобразовать из последовательности R в последовательность Task<L>:

IEnumerable<Task<L>> mappingTasks = remoteItems.Select(remoteItem => MapToLocalObject(remoteItem));

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

Ваш результат - последовательность задач - последовательность будущих L результатов. Для работы с последовательностями задач существует несколько общих операций. Task.WhenAll и Task.WhenAny являются встроенными операциями для наиболее распространенных требований. Если вы хотите дождаться завершения всех сопоставлений, вы можете сделать следующее:

L[] mappedItems = await Task.WhenAll(mappingTasks);

Если вы предпочитаете обрабатывать каждый элемент по мере его завершения, вы можете использовать OrderByCompletion из мою библиотеку AsyncEx:

Task<L>[] orderedMappingTasks = mappingTasks.OrderByCompletion();
foreach (var task in orderedMappingTasks)
{
  var mappedItem = await task;
  ...
}