Как правильно писать Parallel.For с асинхронными методами

Как бы я структурировал код ниже, чтобы вызвать метод async?

Parallel.For(0, elevations.Count(), delegate(int i)
{
   allSheets.AddRange(await BuildSheetsAsync(userID, elevations[i], includeLabels));
});

Ответ 1

Parallel.For() не работает с методами async. Если вам не нужно ограничивать степень parallelism (т.е. вы все в порядке со всеми выполняемыми задачами одновременно), вы можете просто запустить все Task, а затем дождаться их завершения:

var tasks = Enumerable.Range(0, elevations.Count())
    .Select(i => BuildSheetsAsync(userID, elevations[i], includeLabels));
List<Bitmap> allSheets = (await Task.WhenAll(tasks)).SelectMany(x => x).ToList();

Ответ 2

Я бы порекомендовал вам взглянуть на этот вопрос, который я задал несколько дней назад, и в конечном итоге ответил сам, в основном я искал параллельный и асинхронный метод ForEach.

Метод использует SemaphoreSlim для обработки вещей параллельно и принимает асинхронные методы как действие ввода.

Вы также можете взглянуть на две ссылки, которые я предоставил в конце моего ответа, они действительно были полезны для реализации такого поведения, и они также содержат другой способ сделать это, используя Partitioner.

Лично мне не понравился Parallel.For, потому что это синхронный вызов, как описано в ссылках, которые я дал; Я хотел все "асинхронно": -)

Вот он: Асинхронная и параллельная загрузка файлов

Ответ 3

Вы можете попробовать этот код, который я использую. он использует foreach и SemaphoreSlim для достижения параллельной асинхронности.

public static class ParallelAsync
{
    public static async Task ForeachAsync<T>(IEnumerable<T> source, int maxParallelCount, Func<T, Task> action)
    {
        using (SemaphoreSlim completeSemphoreSlim = new SemaphoreSlim(1))
        using (SemaphoreSlim taskCountLimitsemaphoreSlim = new SemaphoreSlim(maxParallelCount))
        {
            await completeSemphoreSlim.WaitAsync();
            int runningtaskCount = source.Count();

            foreach (var item in source)
            {
                await taskCountLimitsemaphoreSlim.WaitAsync();

                Task.Run(async () =>
                {
                    try
                    {
                        await action(item).ContinueWith(task =>
                        {
                            Interlocked.Decrement(ref runningtaskCount);
                            if (runningtaskCount == 0)
                            {
                                completeSemphoreSlim.Release();
                            }
                        });
                    }
                    finally
                    {
                        taskCountLimitsemaphoreSlim.Release();
                    }
                }).GetHashCode();
            }

            await completeSemphoreSlim.WaitAsync();
        }
    }
}

использование:

string[] a = new string[] {
    "1",
    "2",
    "3",
    "4",
    "5",
    "6",
    "7",
    "8",
    "9",
    "10",
    "11",
    "12",
    "13",
    "14",
    "15",
    "16",
    "17",
    "18",
    "19",
    "20"
};

Random random = new Random();

await ParallelAsync.ForeachAsync(a, 2, async item =>
{
    Console.WriteLine(item + " start");

    await Task.Delay(random.Next(1500, 3000));
    Console.WriteLine(item + " end");
});

Console.WriteLine("All finished");

любое предложение, пожалуйста, дайте мне знать.

Ответ 4

Самый простой способ вызвать ваш асинхронный метод внутри Parallel.For следующий:

Parallel.For(0, elevations.Count(), async i =>
{
   allSheets.AddRange(await BuildSheetsAsync(userID, elevations[i], includeLabels));
});

==============

MarioDS в комментарии полностью упоминается, что в этом случае у вас могут быть незаметные исключения. И это определенно очень важная вещь, которую вы всегда должны учитывать, а затем иметь дело с асинхронными делегатами.

В этом случае, если вы считаете, что у вас есть исключения, вы можете использовать блок try/catch внутри делегата. Или в некоторых случаях, если ваша ситуация подходит для вас, вы можете подписаться на TaskScheduler.UnobservedTaskException событие.