Вы должны знать, что такое IEnumerable.
IEnumerable представляет последовательность элементов. Он содержит все, чтобы перечислить эту последовательность: вы можете запросить первый элемент, и как только у вас есть элемент, вы можете запросить следующий, пока есть следующий.
В его На самом низком уровне это перечисление выполняется с помощью таких функций, как GetEnumerator и MoveNext. Получив элемент, вы можете получить к нему доступ через свойство Current.
IEnumerable<Order> orders = ...;
var orderEnumerator = orders.GetEnumerator();
while (orderEnumerator.MoveNext())
{
Order order = orderEnumerator.Current;
ProcessOrder(order);
}
Если вы используете foreach, компилятор вызовет GetEnumerator () и MoveNext () / Current для вас.
Если вы посмотрите ближе к LINQ, вы увидите, что есть два типа методов LINQ: те, которые возвращают IEnumerable<...>
, и те, которые этого не делают.
Примерами первой группы являются: Select, GroupBy, (Group) Join, et c. Все они используют отложенное выполнение: конкатенация методов LINQ этой группы не начнет перечислять последовательность. Результат функции в этой группе представляет другую перечисляемую последовательность: последовательность не перечисляется.
Примерами последней группы являются ToList (), ToDictionary (), Count (), FirstOrDefault (), Any () , Как только вы вызываете их, последовательность перечисляется, или, если быть точным, вызывается GetEnumerator () и вызывается MoveNext (), пока не будет известен результат функции. ToList () будет вызывать метод MoveNext () до тех пор, пока MoveNext () не вернет false, указывая, что больше нет элементов. Для Any () MoveNext () вызывается только один раз:
public static bool Any<TSource>(this IEnumerable<TSource> source)
{
IEnumerator<TSource> enumerator = source.GetEnumerator();
return enumerator.MoveNext();
}
Если MoveNext возвращает true, вы знаете, что последовательность содержит хотя бы один элемент.
Вернуться к вашему вопросу
Если вы используете отладчик для просмотра значения SomeData
, вы увидите, что это IEnumerable<...>
: запрос еще не выполнен.
Даже код
someData.Select(x => x.SomeAsyncThing)
не выполняет запрос. Ваш отладчик скажет, что это все еще IEnumerable<...>
Мне интересно, будет ли это работать, или выбор будет создавать копию списка и ждать эту копию
Да, это будет работать, и нет, Select не будет создавать копию списка, потому что до тех пор, пока не будет списка List.
Task.WhenAll
принимает IEnumerable<Task<TResult>>
в качестве ввода. Если вы посмотрите на справочный источник Task.WhenAll , вы увидите, что он сначала проверяет, является ли ввод массивом или ICollection. Если нет, он будет использовать foreach для преобразования вашей последовательности в Список задач.
В этот момент метод repo.AsyncThing (...) вызывается впервые , используя xA и xB первого элемента previousResult.
Ваш поток будет продолжать выполнять операторы внутри AsyncThing, пока не увидит ожидание (или пока не будет возвращено задание). Поток go увеличит стек вызовов и выполнит следующий элемент последовательности: repo.AsyncThing
с xA и xB второго элемента previousResult.
Это продолжается до тех пор, пока не будет задана полная последовательность задач. в списке. После этого ваш поток ожидает, пока все задачи в списке не будут завершены.
То есть мой код верен?
Это зависит от того, что вы хотите сделать. Поскольку вы также выбираете Id, мне кажется, что в результате вы хотите получить последовательность идентификаторов вместе с ожидаемым результатом задачи.
Если вы хотите получить доступ к элементам этой последовательности, ваш код не хорошо.
Почему?
Как я писал ранее: someData является IEnumerable. Как только вы думаете, что все задачи завершены, и вам нужен первый Id и первый SomeAsyncThing, вы начинаете перечисление и снова вызывается _repo.AsyncThing, в результате чего возникает новая ожидаемая задача.
Я думаю, что вы Я не хочу запускать задачи каждый раз, когда начинаешь перечислять последовательность.
Мой совет:
var someData = previousResult
.Select(x => new
{
Id = x.Id,
SomeAsyncThing = await _repo.AsyncThing(x.A, x.B)
});
Обратите внимание на ожидание перед _repo.AsyncThing.
Как и прежде: пока список не перечислен, AsyncThing не вызывается. Если вы хотите быть уверены, что он будет вызываться только один раз, преобразуйте someData в List или Array, а затем используйте Task.WhenAll:
var someDataList = someData.ToList();
Теперь каждый элемент someDataList является объектом с Id и ожидаемая задача.
Примечание: перечисление someDataList не запустит задачу снова. После выполнения ToList инструкции LINQ в SomeData, только результат находится в SomeDataList.
Все, что вам нужно сделать, это дождаться каждой задачи:
await Task.WhenAll(someDataList.Select(someData => someData.SomeAsyncThing).
Примечание: это не меняет someDataList , это все еще список, где каждый элемент имеет идентификатор и ожидаемую задачу. Единственное, что изменилось, это то, что вы уверены, что каждая ожидаемая задача завершена.
Таким образом, чтобы получить доступ к результатам задач:
var taskResults = someDataList.Select(someData => new
{
Id = someData.Id,
TaskResult = someData.SomeAsyncThing.Result,
});
Примечание: к настоящему времени вы должны знать, что taskResults еще не перечислен. Поэтому результаты задач не извлекаются до тех пор, пока вы не переместите MoveNext () к элементу. Вы, вероятно, закончите это с помощью:
var taskResultCollection = taskResults.ToList ();