То, выполняется ли ваш код асинхронно, зависит от вашего определения асинхронности. Если вы действительно внимательно посмотрите, что происходит, будет только один поток, который будет делать вещи. Однако этот поток не будет ждать без дела, пока у него есть чем заняться.
То, что действительно помогло мне понять async-await, - это аналогия с приготовлением завтрака в в этом интервью сЭрик Липперт . Найдите где-то посередине асинхронное ожидание.
Предположим, что повар готовит завтрак. Он начинает кипятить чай. Вместо того чтобы ждать, пока вода приготовится, он вставляет хлеб в тостер. Не дожидаясь ожидания, он начинает кипятить яйца. Как только чай закипает, он заваривает чай и ждет тост или яйца.
Асинхронное ожидание аналогично. Всякий раз, когда вашему потоку придется ждать завершения другого процесса, например, записи файла, запроса к базе данных для возврата данных, загрузки данных из Интернета, поток не будет бездействовать в ожидании завершения другого процесса, но будетподнимитесь по стеку вызовов, чтобы увидеть, не ожидает ли кто-либо из вызывающих абонентов, и начинает выполнять операторы, пока не увидит ожидание. Снова поднимитесь вверх по стеку вызовов и выполняйте до момента ожидания. и т.д.
Поскольку GetAllAsync
объявлен асинхронным, вы можете быть уверены, что в нем есть ожидание. Фактически, ваш компилятор предупредит вас, если вы объявите функцию асинхронной без ожидания в ней.
Ваш поток перейдет в _customerApi.GetAllAsync("some_url");
и выполнит операторы, пока не увидит ожидание. Если задача, которую ожидает ваш поток, не завершена, поток поднимается вверх по стеку вызовов (ваша процедура) и начинает выполнять следующий оператор: _orderApi.GetAllAsync("some_url")
. Он выполняет операторы, пока не увидит ожидание. Ваша функция снова получает управление и вызывает следующий метод.
Это продолжается до тех пор, пока ваша процедура не начнет ожидать. В этом случае ожидаемый метод Task.WhenAll
(не путать с ненастоящим Task.WaitAll
).
Даже сейчас поток не будет бездействовать, он будет подниматься по стеку вызовови выполнять операторы до тех пор, пока они не встретят ожидание, снова не поднимутся вверх по стеку вызовов и т. д. Пока ваш поток занят выполнением операторов первого вызова метода, никакие операторы второго вызова не будут выполняться, и пока выполняются операторы второго вызова, никакие операторы первого вызова не будут выполняться, даже если первый ожидаетготов.
Это похоже на единственное блюдо: пока он вставляет хлеб в тостер, он не может обработать кипящую воду для чая: только после того, как хлеб будет вставлен, он начнетне дожидаясь, пока он поджарится, он может продолжать заваривать чай.
await Task.WhenAll
ничем не отличается от других ожиданий, за исключением того, что задача завершена, когда все задачи завершены. Таким образом, пока ни одна из задач не готова, ваш поток не будет выполнять операторы после WhenAll. Однако: ваш поток не будет ждать бездействия, он поднимется в стек вызовов и начнет выполнять операторы.
Так что, хотя кажется, что два куска кода выполняются одновременно, это не так. Если вы действительно хотите, чтобы два куска кода выполнялись одновременно, вам придется нанять нового повара, используя `Task.Run (() => SliceTomatoes);
Наем нового повара (начало нового потока) - этоимеет смысл только в том случае, если другая задача не является асинхронной, и у вашего потока есть другие важные действия, такие как поддержание адаптивности пользовательского интерфейса. Обычно твой повар сам бы нарезал помидоры. Пусть ваш абонент решит, нанять ли он нового повара (= вас), чтобы приготовить завтрак и нарезать помидоры.
Я немного упростил это, сказав, что здесь задействована только одна тема (повар). Фактически, это может быть любой поток, который продолжает выполнять операторы после вашего ожидания. Вы можете увидеть, что в отладчике, изучив идентификатор потока, довольно часто это будет другой поток, который будет продолжаться. Однако этот поток имеет тот же контекст, что и ваш исходный поток, поэтому для вас это будет так же, как если бы это был тот же поток: нет необходимости в мьютексе, нет необходимости в IsInvokeRequired
для потоков пользовательского интерфейса. Более подробную информацию об этом можно найти в статьях Стивена Клири