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.