Как добавить асинхронный "ожидание" в оператор выбора добавления?
У меня есть такая функция:
public async Task<SomeViewModel> SampleFunction()
{
var data = service.GetData();
var myList = new List<SomeViewModel>();
myList.AddRange(data.select(x => new SomeViewModel
{
Id = x.Id,
DateCreated = x.DateCreated,
Data = await service.GetSomeDataById(x.Id)
}
return myList;
}
My await
не работает, поскольку его можно использовать только в методе или лямбда, отмеченном модификатором async
. Где я могу разместить async
с помощью этой функции?
Ответы
Ответ 1
Вы можете использовать только await
внутри метода/делегата async
. В этом случае вы должны отметить это лямбда-выражение как async
.
Но подождите, там еще...
Select
относится к периоду до async
, поэтому он не обрабатывает async
lambdas (в вашем случае он возвратит IEnumerable<Task<SomeViewModel>>
вместо IEnumerable<SomeViewModel>
, что вам действительно нужно).
Однако вы можете добавить эту функциональность самостоятельно (желательно как метод расширения), но вам нужно рассмотреть, хотите ли вы await
каждого элемента, прежде чем переходить к следующему (последовательному) или await
всем элементам вместе end (одновременно).
Последовательный async
static async Task<TResult[]> SelectAsync<TItem, TResult>(this IEnumerable<TItem> enumerable, Func<TItem, Task<TResult>> selector)
{
var results = new List<TResult>();
foreach (var item in enumerable)
{
results.Add(await selector(item));
}
return results.ToArray();
}
Параллельный async
static Task<TResult[]> SelectAsync<TItem, TResult>(this IEnumerable<TItem> enumerable, Func<TItem, Task<TResult>> selector)
{
return Task.WhenAll(enumerable.Select(selector));
}
Использование
public Task<SomeViewModel[]> SampleFunction()
{
return service.GetData().SelectAsync(async x => new SomeViewModel
{
Id = x.Id,
DateCreated = x.DateCreated,
Data = await service.GetSomeDataById(x.Id)
}
}
Ответ 2
Вы используете await
внутри лямбда, и эта лямбда будет преобразована в свой отдельный именованный метод компилятором. Чтобы использовать await
, он должен быть async
, а не просто определен в методе async
. Когда вы создаете лямбда async
, у вас теперь есть последовательность задач, которые вы хотите перевести в последовательность своих результатов асинхронно. Task.WhenAll
делает именно это, поэтому мы можем передать наш новый запрос в WhenAll
, чтобы получить задачу, представляющую наши результаты, что именно этот метод хочет вернуть:
public Task<SomeViewModel[]> SampleFunction()
{
return Task.WhenAll(service.GetData().Select(
async x => new SomeViewModel
{
Id = x.Id,
DateCreated = x.DateCreated,
Data = await service.GetSomeDataById(x.Id)
}));
}
Ответ 3
Хотя, возможно, слишком тяжелый для вашего случая использования, используя TPL Dataflow, вы получите более тонкий контроль над обработкой async.
public async Task<List<SomeViewModel>> SampleFunction()
{
var data = service.GetData();
var transformBlock = new TransformBlock<X, SomeViewModel>(
async x => new SomeViewModel
{
Id = x.Id,
DateCreated = x.DateCreated,
Data = await service.GetSomeDataById(x.Id)
},
new ExecutionDataflowBlockOptions
{
// Let 8 "service.GetSomeDataById" calls run at once.
MaxDegreeOfParallelism = 8
});
var result = new List<SomeViewModel>();
var actionBlock = new ActionBlock<SomeViewModel>(
vm => result.Add(vm));
transformBlock.LinkTo(actionBlock,
new DataflowLinkOptions { PropagateCompletion = true });
foreach (var x in data)
{
transformBlock.Post(x);
}
transformBlock.Complete();
await actionBlock.Completion;
return result;
}
Это может быть значительно менее длинным, если service.GetData()
возвратил IObservable<X>
, и этот метод возвратил IObservable<SomeViewModel>
.