Использование async / ожидание нескольких задач

Я использую API-клиент, который полностью асинхронен, то есть каждая операция возвращает Task или Task , например:

 static async Task DoSomething(int siteId, int postId, IBlogClient client) { await client.DeletePost(siteId, postId); // call API client Console.WriteLine("Deleted post {0}.", siteId); } 

Используя операторы async / wait C # 5, каков правильный / наиболее эффективный способ запуска нескольких задач и ждать их завершения:

 int[] ids = new[] { 1, 2, 3, 4, 5 }; Parallel.ForEach(ids, i => DoSomething(1, i, blogClient).Wait()); 

или:

 int[] ids = new[] { 1, 2, 3, 4, 5 }; Task.WaitAll(ids.Select(i => DoSomething(1, i, blogClient)).ToArray()); 

Поскольку клиент API использует внутренне HttpClient, я ожидаю, что это немедленно выдает 5 HTTP-запросов, записывая их на консоль по мере завершения каждого из них.

4 Solutions collect form web for “Использование async / ожидание нескольких задач”

 int[] ids = new[] { 1, 2, 3, 4, 5 }; Parallel.ForEach(ids, i => DoSomething(1, i, blogClient).Wait()); 

Хотя вы выполняете операции параллельно с указанным выше кодом, этот код блокирует каждый stream, в котором выполняется каждая операция. Например, если сетевой вызов занимает 2 секунды, каждый stream висит в течение 2 секунд без каких-либо действий, кроме ожидания.

 int[] ids = new[] { 1, 2, 3, 4, 5 }; Task.WaitAll(ids.Select(i => DoSomething(1, i, blogClient)).ToArray()); 

С другой стороны, приведенный выше код с WaitAll также блокирует streamи, и ваши streamи не будут свободны обрабатывать любую другую работу до завершения операции.

Рекомендуемый подход

Я бы предпочел WhenAll который будет выполнять ваши операции асинхронно в Parallel.

 public async Task DoWork() { int[] ids = new[] { 1, 2, 3, 4, 5 }; await Task.WhenAll(ids.Select(i => DoSomething(1, i, blogClient))); } 

Фактически, в приведенном выше случае вам даже не нужно await , вы можете просто вернуться из метода, поскольку у вас нет никаких продолжений:

 public Task DoWork() { int[] ids = new[] { 1, 2, 3, 4, 5 }; return Task.WhenAll(ids.Select(i => DoSomething(1, i, blogClient))); } 

Чтобы поддержать это, вот подробное сообщение в блоге, в котором рассматриваются все альтернативы и их преимущества / недостатки: как и где параллельный asynchronous ввод-вывод с ASP.NET Web API

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

Вот код:

 using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace AsyncTest { class Program { class Worker { public int Id; public int SleepTimeout; public async Task DoWork(DateTime testStart) { var workerStart = DateTime.Now; Console.WriteLine("Worker {0} started on thread {1}, beginning {2} seconds after test start.", Id, Thread.CurrentThread.ManagedThreadId, (workerStart-testStart).TotalSeconds.ToString("F2")); await Task.Run(() => Thread.Sleep(SleepTimeout)); var workerEnd = DateTime.Now; Console.WriteLine("Worker {0} stopped; the worker took {1} seconds, and it finished {2} seconds after the test start.", Id, (workerEnd-workerStart).TotalSeconds.ToString("F2"), (workerEnd-testStart).TotalSeconds.ToString("F2")); } } static void Main(string[] args) { var workers = new List { new Worker { Id = 1, SleepTimeout = 1000 }, new Worker { Id = 2, SleepTimeout = 2000 }, new Worker { Id = 3, SleepTimeout = 3000 }, new Worker { Id = 4, SleepTimeout = 4000 }, new Worker { Id = 5, SleepTimeout = 5000 }, }; var startTime = DateTime.Now; Console.WriteLine("Starting test: Parallel.ForEach..."); PerformTest_ParallelForEach(workers, startTime); var endTime = DateTime.Now; Console.WriteLine("Test finished after {0} seconds.\n", (endTime - startTime).TotalSeconds.ToString("F2")); startTime = DateTime.Now; Console.WriteLine("Starting test: Task.WaitAll..."); PerformTest_TaskWaitAll(workers, startTime); endTime = DateTime.Now; Console.WriteLine("Test finished after {0} seconds.\n", (endTime - startTime).TotalSeconds.ToString("F2")); startTime = DateTime.Now; Console.WriteLine("Starting test: Task.WhenAll..."); var task = PerformTest_TaskWhenAll(workers, startTime); task.Wait(); endTime = DateTime.Now; Console.WriteLine("Test finished after {0} seconds.\n", (endTime - startTime).TotalSeconds.ToString("F2")); Console.ReadKey(); } static void PerformTest_ParallelForEach(List workers, DateTime testStart) { Parallel.ForEach(workers, worker => worker.DoWork(testStart).Wait()); } static void PerformTest_TaskWaitAll(List workers, DateTime testStart) { Task.WaitAll(workers.Select(worker => worker.DoWork(testStart)).ToArray()); } static Task PerformTest_TaskWhenAll(List workers, DateTime testStart) { return Task.WhenAll(workers.Select(worker => worker.DoWork(testStart))); } } } 

И результат:

 Starting test: Parallel.ForEach... Worker 1 started on thread 1, beginning 0.21 seconds after test start. Worker 4 started on thread 5, beginning 0.21 seconds after test start. Worker 2 started on thread 3, beginning 0.21 seconds after test start. Worker 5 started on thread 6, beginning 0.21 seconds after test start. Worker 3 started on thread 4, beginning 0.21 seconds after test start. Worker 1 stopped; the worker took 1.90 seconds, and it finished 2.11 seconds after the test start. Worker 2 stopped; the worker took 3.89 seconds, and it finished 4.10 seconds after the test start. Worker 3 stopped; the worker took 5.89 seconds, and it finished 6.10 seconds after the test start. Worker 4 stopped; the worker took 5.90 seconds, and it finished 6.11 seconds after the test start. Worker 5 stopped; the worker took 8.89 seconds, and it finished 9.10 seconds after the test start. Test finished after 9.10 seconds. Starting test: Task.WaitAll... Worker 1 started on thread 1, beginning 0.01 seconds after test start. Worker 2 started on thread 1, beginning 0.01 seconds after test start. Worker 3 started on thread 1, beginning 0.01 seconds after test start. Worker 4 started on thread 1, beginning 0.01 seconds after test start. Worker 5 started on thread 1, beginning 0.01 seconds after test start. Worker 1 stopped; the worker took 1.00 seconds, and it finished 1.01 seconds after the test start. Worker 2 stopped; the worker took 2.00 seconds, and it finished 2.01 seconds after the test start. Worker 3 stopped; the worker took 3.00 seconds, and it finished 3.01 seconds after the test start. Worker 4 stopped; the worker took 4.00 seconds, and it finished 4.01 seconds after the test start. Worker 5 stopped; the worker took 5.00 seconds, and it finished 5.01 seconds after the test start. Test finished after 5.01 seconds. Starting test: Task.WhenAll... Worker 1 started on thread 1, beginning 0.00 seconds after test start. Worker 2 started on thread 1, beginning 0.00 seconds after test start. Worker 3 started on thread 1, beginning 0.00 seconds after test start. Worker 4 started on thread 1, beginning 0.00 seconds after test start. Worker 5 started on thread 1, beginning 0.00 seconds after test start. Worker 1 stopped; the worker took 1.00 seconds, and it finished 1.00 seconds after the test start. Worker 2 stopped; the worker took 2.00 seconds, and it finished 2.00 seconds after the test start. Worker 3 stopped; the worker took 3.00 seconds, and it finished 3.00 seconds after the test start. Worker 4 stopped; the worker took 4.00 seconds, and it finished 4.00 seconds after the test start. Worker 5 stopped; the worker took 5.00 seconds, and it finished 5.00 seconds after the test start. Test finished after 5.00 seconds. 

Поскольку API, который вы вызываете, является асинхронным, версия Parallel.ForEach не имеет большого смысла. Вы не должны использовать .Wait в версии WaitAll так как это потеряет параллелизм. Другая альтернатива, если вызывающий абонент использует async, использует Task.WhenAll после выполнения Select и ToArray для создания массива задач. Второй альтернативой является использование Rx 2.0

Вы можете использовать функцию Task.WhenAll которую вы можете передавать n задач; Task.WhenAll вернет задачу, которая завершается, когда все задачи, которые вы передали в Task.WhenAll завершены. Вы должны ждать асинхронно на Task.WhenAll чтобы вы не блокировали свой stream пользовательского интерфейса:

  public async Task DoSomeThing() { var Task[] tasks = new Task[numTasks]; for(int i = 0; i < numTask; i++) { tasks[i] = CallSomeAsync(); } await Task.WhenAll(tasks); // code that'll execute on UI thread } 
  • Метод общего назначения FromEvent
  • Вложенность в Parallel.ForEach
  • В чем разница между задачей и streamом?
  • Каков наилучший способ поймать исключение в Задаче?
  • Лучшая практика для вызова ConfigureAwait для всего кода на стороне сервера
  • Как SynchronizationContext.Current основного streamа становится нулевым в приложении Windows Forms?
  • Самый простой способ сделать огонь и забыть метод в c # 4.0
  • Глобальный обработчик исключений TAP
  • В чем преимущество использования async с MVC5?
  • Как обрабатывать все необработанные исключения при использовании параллельной библиотеки задач?
  • Синхронно ждет асинхронной операции и почему Wait () заморозит программу здесь
  • Давайте будем гением компьютера.