Почему методы Parallel.For/Foreach не работают с асинхронной лямбдой?

Рейтинг: 1Ответов: 1Опубликовано: 10.06.2023

Есть следующий кусок кода:

private static void WorkAsync()
{
    Parallel.For(0,
        20,
         async i =>
         {
             await Task.Delay(50000);
            Console.WriteLine(i);
        });
}

При вызове метода WorkAsync, он практически сразу возвращает управление вызывающему коду, хотя цикл For ещё не завершился. Я правильно понимаю, что асинхронная лямбда при каждом вызове возвращает управление вызывающему потоку и поэтому не происходит ожидание?

Ответы

▲ 2Принят

Ваша лямбда это async void, а async void невозможно ожидать.

Для асинхронных вызовов можно не использовать Parallel. Тем более у Parallel.For блокирующее синхронное ожидание, что для асинхронной среды не хорошо.

Как-то так это делается:

private static async Task WorkAsync()
{
    List<Task> tasks = new();
    for (int i = 0; i < 20; i++)
    {
        tasks.Add(JobAsync(i));
    }
    await Task.WhenAll(tasks);
}

private static async Task JobAsync(int i)
{
    await Task.Delay(50000);
    Console.WriteLine(i);
}

Вызов выглядит так:

await WorkAsync();

Если очень хочется, можно через лямбду:

private static async Task WorkAsync()
{
    List<Task> tasks = new();
    for (int i = 0; i < 20; i++)
    {
        tasks.Add(((Func<int, Task>)(async index =>
        {
            await Task.Delay(50000);
            Console.WriteLine(index);
        }))(i));
    }
    await Task.WhenAll(tasks);
}

Или так:

private static async Task WorkAsync()
{
    List<Task> tasks = new();
    Func<int, Task> func = async index =>
    {
        await Task.Delay(50000);
        Console.WriteLine(index);
    };
    for (int i = 0; i < 20; i++)
    {
        tasks.Add(func(i));
    }
    await Task.WhenAll(tasks);
}

Можно даже с ограничением на количество одновременно выполняемых задач: Массовые асинхронные вызовы с ограничением на количество параллельных без семафора