Загрузка файла - это связанный с вводом / выводом вызов, параллельный или нет. Первое, что вы должны убедиться, это то, что вы делаете асинхронный вызов без потока для загрузки одного файла. Такие методы, как Task.Run, task.Start, основаны на потоках, и их не следует использовать для вызовов, связанных с вводом / выводом, в противном случае вы начнете загружать файлы параллельно, но вы сразу же заблокируете весь свой ЦП, каждое ядро в режиме ожидания ожидает вызов загрузки, чтобы вернуться.
Вместо этого вы должны использовать шаблон async / await и ждать вашего метода асинхронной загрузки.
Это означает, что у вас есть настоящий асинхронный метод загрузки, но большинство библиотек это предоставляют.
Теперь, если вы распараллелите этот вызов ввода-вывода, сохраните все возвращенные задачи в коллекции, и в конце вы сможете использовать await Tasks.WhenAll (tasks); ждать всех задач.
Одной вещью, которую вам также необходимо убедиться при выполнении одновременных асинхронных вызовов, связанных с вводом / выводом, является не истощение пула соединений ввода / вывода, поэтому вы можете захотеть ограничить количество одновременных вызовов ввода / вывода, которые вы делаете.
Я реализовал API параллельной обработки, который позволяет вам выполнять параллельные асинхронные вызовы без потоков с опциями регулирования ввода-вывода и т. Д.
Не стесняйтесь взглянуть и использовать: https://www.nuget.org/packages/ParallelProcessor/