Ответ 1
Это очень хороший вопрос, и понимание этого является ключевым для понимания того, почему асинхронный IO так важен. Причина, по которой новая функция async/await была добавлена в С# 5.0, заключается в упрощении написания асинхронного кода. Поддержка асинхронной обработки на сервере не нова, но существует с ASP.NET 2.0.
Как Стив показал вам, с синхронной обработкой, каждый запрос в ASP.NET(и WCF) берет один поток из пула потоков. Проблема, которую он продемонстрировал, - это хорошо известная проблема под названием " головоломка пула потоков". Если вы выполняете синхронный ввод-вывод на своем сервере, поток пула потоков останется заблокированным (ничего не делая) на время ввода-вывода. Поскольку число потоков в пуле потоков ограничено, при загрузке это может привести к ситуации, когда все потоки пулов потоков блокируются в ожидании ввода-вывода, а запросы запускаются в очередь, что приводит к увеличению времени отклика. Поскольку все потоки ждут завершения ввода-вывода, вы увидите, что занятость процессора близка к 0% (даже если время ответа проходит через крышу).
Что вы спрашиваете (Почему мы не можем просто использовать более длинный поток?) - очень хороший вопрос. На самом деле, именно так большинство людей решают проблему голодания пула потоков до сих пор: просто больше потоков в пуле потоков. В некоторых документах Microsoft даже указывается, что в качестве исправления ситуации, когда может возникнуть голод пула потоков. Это приемлемое решение, и до С# 5.0 было намного проще сделать это, чем переписать код полностью асинхронным.
Есть несколько проблем с подходом:
-
Нет значения, которое работает во всех ситуациях: количество потоков потоков потоков, которые вам понадобятся, зависит линейно от продолжительности ввода-вывода и нагрузки на вашем сервере. К сожалению, задержка ввода-вывода в основном непредсказуема. Вот пример: Скажем, вы делаете HTTP-запросы к стороннему веб-сервису в своем приложении ASP.NET, для завершения которого требуется около 2 секунд. Вы сталкиваетесь с голоданием пула потоков, поэтому вы решили увеличить размер пула потоков, скажем, 200 потоков, а затем он снова начнет работать нормально. Проблема в том, что, возможно, на следующей неделе веб-служба будет иметь технические проблемы, которые увеличивают время отклика до 10 секунд. Все внезапное голодание пула потоков возвращается, потому что потоки блокируются в 5 раз дольше, поэтому теперь вам нужно увеличить число 5 раз, до 1000 потоков.
-
Масштабируемость и производительность. Вторая проблема заключается в том, что если вы это сделаете, вы по-прежнему будете использовать один поток для каждого запроса. Темы - дорогой ресурс. Для каждого управляемого потока в .NET требуется выделение памяти в 1 МБ для стека. Для веб-страницы, создающей IO, которая длится 5 секунд и с нагрузкой 500 запросов в секунду вам потребуется 2,500 потоков в пуле потоков, что означает 2,5 ГБ памяти для стеков потоков, которые не будут ничего делать. Тогда у вас возникнет проблема переключения контекста, что скажется на производительности вашего компьютера (затрагивая все службы на компьютере, а не только ваше веб-приложение). Несмотря на то, что Windows выполняет довольно хорошую работу по игнорированию ожидающих потоков, она не предназначена для обработки такого большого количества потоков. Помните, что максимальная эффективность достигается, когда количество потоков, равное количеству логических процессоров на машине (обычно не более 16).
Таким образом, увеличение размера пула потоков - это решение, и люди занимаются этим в течение десятилетия (даже в собственных продуктах Microsoft), он является менее масштабируемым и эффективным с точки зрения использования памяти и процессора, а вы всегда находятся во власти внезапного увеличения задержки ввода-вывода, которая может вызвать голод. До С# 5.0 сложность асинхронного кода не стоила больших проблем для многих людей. async/await меняет все так же, как сейчас, вы можете воспользоваться масштабируемостью асинхронного ввода-вывода и писать простой код в то же время.
Подробнее: http://msdn.microsoft.com/en-us/library/ff647787.aspx"Использовать асинхронные вызовы для вызова веб-служб или удаленных объектов, когда есть возможность выполнить дополнительную параллельную обработку при продолжении вызова веб-службы. По возможности избегайте синхронных (блокирующих) вызовов веб-служб, поскольку исходящие вызовы веб-служб выполняются с использованием потоков из пула потоков ASP.NET. Блокирование вызовов уменьшает количество доступных потоков для обработки других входящих запросов.