Я полагал, что любой вид асинхронного выполнения создает поток в невидимой области.
Это ваша проблема - на самом деле это не так.
Дело в том,весь ваш компьютер фактически асинхронен - запросы к оперативной памяти, связь через сетевую карту, доступ к жесткому диску ... все это по своей сути асинхронные операции.
Современные ОС фактически построены на асинхронном вводе-выводе.Например, даже если вы выполняете синхронный запрос файла (например, File.ReadAllText
), ОС отправляет асинхронный запрос.Однако вместо того, чтобы вернуть управление вашему коду, он блокирует , ожидая ответа на асинхронный запрос.И именно здесь приходит правильный асинхронный код - вместо ожидания ответа вы предоставляете запросу callback - функцию, которая будет выполняться при возврате ответа.
На времяв асинхронном запросе нет потока .Все это происходит на совершенно другом уровне - скажем, запрос отправляется в прошивку на вашей сетевой карте и ему дается адрес DMA для заполнения ответа.Когда NIC завершает ваш запрос, он заполняет память и сигнализирует прерывание процессору.Ядро ОС обрабатывает прерывание, сигнализируя приложению-владельцу (обычно «канал» IOCP), что запрос выполнен.Все это по-прежнему выполняется без какого-либо потока - только на короткое время в конце поток заимствуется (в .NET это из пула потоков IOCP) для выполнения обратного вызова.
Итак, представьте себепростой сценарий.Вам нужно отправить 100 одновременных запросов к базе данных.С многопоточностью вы бы запускали новую ветку для каждого из этих запросов.Это означает сотню потоков, сотни потоков, стоимость запуска самого нового потока (запуск нового потока стоит дешево - запуск сотен одновременно, не так много), совсем немного ресурсов.И эти темы просто ... заблокируют.Ничего не делать.Когда приходит ответ, потоки просыпаются один за другим и в конечном итоге удаляются.
С другой стороны, с помощью асинхронного ввода-вывода вы можете просто отправить все запросы из одного потока - и зарегистрироватьобратный вызов, когда каждый из них закончен.Сотня одновременных запросов будет стоить вам только вашего исходного потока (который будет свободен для другой работы, как только запросы будут опубликованы), и короткое время с потоками из пула потоков, когда запросы будут завершены - в «худшем» случае,примерно столько потоков, сколько у вас процессорных ядер.Конечно, при условии, что вы не используете код блокировки в обратном вызове:)
Это не обязательно означает, что асинхронный код автоматически становится более эффективным.Если вам нужен только один запрос, и вы ничего не можете сделать, пока не получите ответ, бессмысленно делать запрос асинхронным.Но в большинстве случаев это не ваш реальный сценарий - например, вам нужно поддерживать графический интерфейс в то же время, или вам нужно делать одновременные запросы, или весь ваш код основан на обратном вызове, а не записывается синхронно (типичныйПриложение .NET Windows Forms в основном основано на событиях).
Реальная выгода от асинхронного кода заключается именно в этом: упрощенный неблокирующий код пользовательского интерфейса (не более предупреждений "(не отвечает)" из оконного менеджера)и значительно улучшенный параллелизм.Если у вас есть веб-сервер, который обрабатывает тысячу запросов одновременно, вы не хотите тратить 1 ГБ адресного пространства только на совершенно ненужные стеки потоков (особенно в 32-битной системе) - вы используете потоки только тогда, когда у вас есть что-тоделать.
Итак, в конце концов, асинхронный код значительно упрощает пользовательский интерфейс и код сервера.В некоторых случаях, в основном с серверами, это также может сделать его намного более эффективным.Повышение эффективности происходит именно благодаря тому, что отсутствует поток во время выполнения асинхронного запроса.
Ваш комментарий относится только к одному конкретному виду асинхронного кода - многопоточному параллелизму.В этом случае вы действительно теряете поток при выполнении запроса.Однако это не то, что люди имеют в виду, когда говорят, что «моя библиотека предлагает асинхронный API» - в конце концов, это 100% бесполезный API;Вы могли бы просто позвонить await Task.Run(TheirAPIMethod)
и получить то же самое.