Асинхронный метод может содержать множество выражений await. Когда система встречает в блоке кода оператор await, то выполнение в асинхронном методе останавливается, пока не завершится асинхронная задача. После завершения задачи управление переходит к следующему оператору await и так далее. Это позволяет вызывать асинхронные задачи последовательно в определенном порядке. Например:
await PrintAsync("Hello C#"); await PrintAsync("Hello World"); await PrintAsync("Hello METANIT.COM"); async Task PrintAsync(string message) { await Task.Delay(2000); // имитация продолжительной операции Console.WriteLine(message); }
Консольный вывод данной программы:
Hello C# Hello World Hello METANIT.COM
То есть мы видим, что вызовы PrintAsync выполняются последовательно в том порядке, в котором они определены в коде. Каждая задача выполняется как минимум 2 секунды, соответственно общее время выполнения трех задач будет как минимум 6 секунд. И в данном случае вывод строго детерминирован.
Нередко такая последовательность бывает необходима, если одна задача зависит от результатов другой.
Однако это не всегда необходимо. В подобном случае мы можем сразу запустить все задачи параллельно и применить оператор await там, где необходимо гарантировать завершение выполнения задачи, например, в самом конце программы.
// определяем и запускаем задачи var task1 = PrintAsync("Hello C#"); var task2 = PrintAsync("Hello World"); var task3 = PrintAsync("Hello METANIT.COM"); // ожидаем завершения задач await task1; await task2; await task3; async Task PrintAsync(string message) { await Task.Delay(2000); // имитация продолжительной операции Console.WriteLine(message); }
В этом случае все задачи запускаются и выполняются параллельно, соответственно общее время выполнения будет меньше 6 секунд, а консольный вывод программы недетерминирован. Например, он может быть следующим:
Hello METANIT.COM Hello C# Hello World
Однако .NET позволяет упростить отслеживание выполнения набора задач с помощью метода Task.WhenAll. Этот метод принимает набор
асинхронных задач и ожидает завершения всех этих задач. Этот метод является аналогом статического метода Task.WaitAll()
, однако
предназначен непосредственно для асинхронных методов и позволяет применять оператор await:
// определяем и запускаем задачи var task1 = PrintAsync("Hello C#"); var task2 = PrintAsync("Hello World"); var task3 = PrintAsync("Hello METANIT.COM"); // ожидаем завершения всех задач await Task.WhenAll(task1, task2, task3); async Task PrintAsync(string message) { await Task.Delay(2000); // имитация продолжительной операции Console.WriteLine(message); }
Вначале запускаются три задачи. Затем Task.WhenAll создает новую задачу, которая будет автоматически выполнена после выполнения всех предоставленных задач, то есть задач task1, task2, task3. А с помощью оператора await ожидаем ее завершения.
Если нам надо дождаться, когда будет выполнена хотя бы одна задача из некоторого набора задач, то можно применять метод Task.WhenAny().
Это аналог метода Task.WaitAny()
- он завершает выполнение, когда завершается хотя бы одна задача. Но для ожидания выполнения к Task.WhenAny() применяется
оператор await:
// определяем и запускаем задачи var task1 = PrintAsync("Hello C#"); var task2 = PrintAsync("Hello World"); var task3 = PrintAsync("Hello METANIT.COM"); // ожидаем завершения хотя бы одной задачи await Task.WhenAny(task1, task2, task3); async Task PrintAsync(string message) { await Task.Delay(new Random().Next(1000, 2000)); // имитация продолжительной операции Console.WriteLine(message); }
Задачи, передаваемые в Task.WhenAll
и Task.WhenAny
, могут возвращать некоторое значение.
В этом случае из методов Task.WhenAll и Task.WhenAny можно получить массив, который будет содержать результаты задач:
// определяем и запускаем задачи var task1 = SquareAsync(4); var task2 = SquareAsync(5); var task3 = SquareAsync(6); // ожидаем завершения всех задач int[] results = await Task.WhenAll(task1, task2, task3); // получаем результаты: foreach (int result in results) Console.WriteLine(result); async Task<int> SquareAsync(int n) { await Task.Delay(1000); return n * n; }
В данном случае метод Square возвращает число int - квадрат передаваемого в метод числа. И переменная results будет содержать результат вызова Task.WhenAll - по сути результаты всех трех запущенных задач. Поскольку все передаваемые в Task.WhenAll задачи возвращают int, то соответственно результат Task.WhenAll будет представлять массив значений int.
Также после завершения задачи ее результат можно получить стандартным образом через свойство Result:
// определяем и запускаем задачи var task1 = SquareAsync(4); var task2 = SquareAsync(5); var task3 = SquareAsync(6); await Task.WhenAll(task1, task2, task3); // получаем результат задачи task2 Console.WriteLine($"task2 result: {task2.Result}"); // task2 result: 25 async Task<int> SquareAsync(int n) { await Task.Delay(1000); return n * n; }