Async в коде LINQ - Уточнение?
Почти каждый SO ответ на этот вопрос гласит, что:
LINQ не работает отлично с async
Также:
Я рекомендую вам не думать об этом как о "использовании async в LINQ"
Но в книге Стивена есть образец для:
Проблема: У вас есть набор задач, которые ждут, и вы хотите сделать некоторые обрабатывать каждую задачу после ее завершения. Однако вы хотите сделать обработка для каждого, как только он завершается, не, ожидая любая из других задач.
Одно из рекомендуемых решений:
static async Task<int> DelayAndReturnAsync(int val)
{
await Task.Delay(TimeSpan.FromSeconds(val));
return val;
}
// This method now prints "1", "2", and "3".
static async Task ProcessTasksAsync()
{
// Create a sequence of tasks.
Task<int> taskA = DelayAndReturnAsync(2);
Task<int> taskB = DelayAndReturnAsync(3);
Task<int> taskC = DelayAndReturnAsync(1);
var tasks = new[] { taskA, taskB, taskC };
var processingTasks = tasks.Select(async t =>
{
var result = await t;
Trace.WriteLine(result);
}).ToArray();
// Await all processing to complete
await Task.WhenAll(processingTasks);
}
Вопрос №1:
Я не понимаю, почему теперь async
внутри оператора LINQ - работает. Разве мы не сказали "не думайте об использовании async
в LINQ"?
Вопрос № 2:
Когда элемент управления достигает await t
здесь - что на самом деле происходит? Оставляет ли элемент метод ProcessTasksAsync
? или он оставляет анонимный метод и продолжает итерацию?
Ответы
Ответ 1
Я не понимаю, почему теперь async внутри оператора LINQ - работает. Разве мы не сказали "не думайте об использовании async в LINQ"?
async
в основном не работает с LINQ, поскольку расширения IEnumerable<T>
не всегда выводят тип делегата правильно и откладывают до Action<T>
. Они не имеют особого понимания класса Task
. Это означает, что фактический делегат async становится async void
, что плохо. В случае Enumerable.Select
у нас есть перегрузка, которая возвращает a Func<T>
(которая в нашем случае будет Func<Task>
в нашем случае), что эквивалентно async Task
, поэтому она отлично работает для асинхронных вариантов использования.
Когда элемент управления достигнет ожидания здесь - что на самом деле происходит? Оставляет ли элемент управления метод ProcessTasksAsync?
Нет, это не так. Enumerable.Select
- это проецирование всех элементов в последовательности. Это означает, что для каждого элемента в коллекции await t
, который вернет управление итератору, который продолжит итерацию всех элементов. Для этого вам нужно await Task.WhenAll
, чтобы все элементы завершили выполнение.
Ответ 2
Вопрос 1:
Разница в том, что каждая задача продолжена с дополнительной обработкой, которая равна: Trace.WriteLine(result);
. В ссылке, на которую вы указали, что код ничего не меняет, просто создает накладные расходы на ожидание и завершение другой задачей.
Вопрос 2:
Когда элемент управления достигнет ожидания здесь - что на самом деле происходит?
Он ожидает результата задачи ProcessTasksAsync
, затем продолжите с помощью Trace.WriteLine(result);
. Мы можем сказать, что элемент управления ProcessTasksAsync
, когда мы получаем результат, и обработка все еще находится внутри анонимного метода.
В конце у нас есть await Task.WhenAll(processingTasks);
, который будет ждать завершения всех задач, включая дополнительную обработку (Trace.WriteLine(result);
), прежде чем продолжить, но каждая задача не ждет других, чтобы продолжить выполнение: Trace.WriteLine(result);
Ответ 3
Так будет лучше:
static async Task<int> DelayAndReturnAsync(int val)
{
await Task.Delay(TimeSpan.FromSeconds(val));
return val;
}
static async Task AwaitAndProcessAsync(Task<int> task)
{
var result = await task;
Console.WriteLine(result);
}
// This method now prints "1", "2", and "3".
static async Task ProcessTasksAsync()
{
// Create a sequence of tasks.
Task<int> taskA = DelayAndReturnAsync(2);
Task<int> taskB = DelayAndReturnAsync(3);
Task<int> taskC = DelayAndReturnAsync(1);
var tasks = new[] { taskA, taskB, taskC };
var processingTasks = tasks.Select(AwaitAndProcessAsync).ToArray();
// Await all processing to complete
await Task.WhenAll(processingTasks);
}
Массив Task, потому что AwaitAndProcessAsync возвращает Task.