Задать вопрос
Jeer
@Jeer
уверенный пользователь

Кто может разжевать асинхронные контроллеры?

Доброго времени!
Различие между синхронными и асинхронными контроллерами из книжки
Чтобы понять различие между синхронными и асинхронными методами, рассмотрим, как IIS обрабатывает входящие запросы. Веб-сервер поддерживает пул потоков, которые обслуживают запросы. При обращении пользователя к веб-ресурсу IIS выделяет поток из пула для обслуживания данного запроса. И пока данный поток не обработает предназначенный для него запрос, другие запросы он обрабатывать не может.

Однако предположим, что метод контроллера в процессе обработки запроса должен выполнить запрос к другому ресурсу или к базе данных. Запрос к сетевому ресурсу или БД сам по себе может занять некоторое время. При синхронной обработке поток, обрабатывающий запрос, временно блокируется, пока сетевой ресурс или БД не возвратят нужные нам данные.

И если обработка запроса блокируется очень долго, то IIS начинает задействовать для обслуживания других входящих запросов новые потоки. Однако есть ограничения на общее количество потоков. Когда количество потоков достигает предела, то вновь входящие запросы помещаются в очередь ожидания. Однако и тут есть ограничение на количество запросов в очереди. И когда это количество превышает предел, то IIS просто отклоняет все остальные запросы с помощью статусного кода 503 (Service Unavailable).

При асинхронной обработке поток не ждет, пока БД вернет ему данные, а начинает обрабатывать запрос от другого пользователя. Но когда, наконец, с сетевого ресурса или БД придут нужные данные, поток возвращается к обработке ранее обрабатываемого запроса в обычном режиме.
Немного кода
public class HomeController : Controller
    {
        BookContext db = new BookContext();
 
        public ActionResult Index()
        {
            IEnumerable<Book> books = db.Books;
            ViewBag.Books = books;
            return View();
        }
 
        // асинхронный метод
        public async Task<ActionResult> BookList()
        {
            IEnumerable<Book> books = await db.Books.ToListAsync();
            ViewBag.Books = books;
            return View("Index");
        }
    }


Собственно, расклад такой, я понимаю, что исторически асинхронность стала крайне востребована в виндовых приложениях, возьмем, например, винформс. Всю программу обрабатывает один поток. Когда мы нажимаем кнопку на визуальной части, которая запускает какие-нибудь долгие вычисления или те же долгие операции внешних вызовов, то визуальная часть программы блокируется (зависает). Я насколько понимаю, это поток выполнения от операционной системы. В этой ситуации на помощь приходит асинхронность, и визуальная часть продолжает работать, так как пока первый поток завис, ОС выделяет дополнительный поток, который продолжает обрабатывать нашу программу.
Я правильно понимаю, что в таком случае именно ОС с её диспетчером выделяет дополнительный поток?
Другая ситуация, когда у нас веб сервер. Как сказано в спойлере, у нас есть пул потоков, который предназначен именно для нашего веб сайта. И вот сам процесс приостановки выполнения при таких асинхронных операциях толком нигде не расписывается.
Давайте немного подробнее. Приходит запрос от пользователя - из пула потоков выделяется поток - обработка доходит до сахарной строчки await, далее наш поток возвращается в пул потоков. Но! Нельзя просто приостановить выполнение программы и освободить ресурсы. Они должны где-то храниться (в оперативке), должны быть ссылки на эти ресурсы для возможности возобновления работы. Для этого операционная система делает свой новый поток (обработчик? тред? как это правильно называется?) и поток из пула возвращается обратно в пул.
Собственно, бывают ситуации, когда асинхронность может ухудшить производительность (например, выполнение await операций в большом цикле)
Еще один очень важный момент, количество потоков пуле можно выставлять самостоятельно. По сути, это просто ограничение на наш сайт, чтобы не отдать ему все ресурсы сервера. Но, чисто теоретически, мы можем выставить это значение в максимум. Тогда проку от асинхронных операций не будет вовсе, лишь один вред из-за накладных расходов.
Один коллега меня уверяет, что асинхронность была придумана не для веба и в вебе её не нужно использовать.
Но с другой стороны, я почитываю статейки, слежу, можно так сказать, за развитием asp.net core. На все операции накидывается асинхронность (в middleware, например), там, где в первых версиях не было асинхронности. Разработчики рекомендуют использовать асинхронные операции там, где это возможно.
И я придерживаюсь подхода, что использование асинхронности в вебе - это правильно. Но и доводы этого товарища звучат убедительно. "А ты докажи, что не Числобог"?
Я запутался. Я не умею делать нагрузочное тестирование, да и думал, что в инете полно результатов. Но я порылся и нашел какую-то индусскую хрень, которая ничего абсолютно не доказывает. Одни слова, скопированные из статьи в статью, что асинхронность - это хорошо.
Мне нужны цифры. Конкретные цифры. Желательно как у меня в спойлере с кодом: есть два полупустых контроллера, синхронный и асинхронный. В которых есть запрос к базе. Нагрузочное тестирование. Цифры. Табличка. С пулом потоков стандартным и выставленном в максимально возможный.
  • Вопрос задан
  • 1816 просмотров
Подписаться 5 Средний 7 комментариев
Решения вопроса 1
@Free_ze
Пишу комментарии в комментарии, а не в ответы
await еще не означает создания дополнительный поток (тред). Особенно, когда дело касается асинхронного ввода-вывода.

В десктопных приложениях в основном потоке есть Главный Цикл (event loop), который в свободное от обработчиков время перемалывает очередь сообщений окна (дёргает эти самые обработчики в ответ на соответствующие события, вроде нажатия кнопок). Если обработчик попался слишком жирный, то сообщения не обрабатываются - окно перестает реагировать на сообщения.

В вебе (ASP.NET) же каждому новому запросу выделяется новый поток. Если запросы выполняются слишком медленно, а клиенты поступают и поступают, то есть риск исчерпать thread pool. Как правило, самые медленные действия - это ввод-вывод, при которых поток нашей программы ничем не занят, кроме ожидания. Но операционные системы могут делать асинхронный ввод-вывод (сигналить, когда данные были прочтены/записаны), так почему бы нам не вернуть напрасно простаивающий поток в тредпул? А когда данные считаются - мы выделим поток и вернемся к задачке.
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы