Асинхронные страницы в платформе ASP.NET - где находятся другие потоки и как они подключены? - PullRequest
9 голосов
/ 04 апреля 2010

Извините за этот тупой вопрос об асинхронных операциях. Вот как я это понимаю.

IIS имеет ограниченный набор рабочих потоков, ожидающих запросов. Если один запрос является длительной операцией, он заблокирует этот поток. Это приводит к меньшему количеству потоков для обслуживания запросов.

Способ исправить это - использовать асинхронные страницы. Когда приходит запрос, основной рабочий поток освобождается, и этот другой поток создается в каком-то другом месте. Таким образом, основной поток может обслуживать другие запросы. Когда запрос завершается в этом другом потоке, другой поток выбирается из основного пула потоков, и ответ отправляется обратно клиенту.

1) Где находятся эти другие темы? Есть ли другой пул потоков?

2) Если ASP.NET нравится создавать новые потоки в этом другом пуле потоков (?), Почему бы не увеличить количество потоков в основном рабочем пуле - они все равно работают на одной машине? Я не вижу преимущества перемещения этого запроса в этот другой пул потоков. Память / ЦП должны быть одинаковыми, верно?

3) Если основной поток передает запрос другому потоку, почему запрос не отключается? Он волшебным образом передает запрос другому рабочему потоку где-то еще, и, когда длительный процесс завершается, он выбирает поток из основного рабочего пула и отправляет ответ клиенту. Я поражен ... но как это работает?

Ответы [ 4 ]

10 голосов
/ 05 апреля 2010

Вы не сказали, какую версию IIS или ASP.NET вы используете. Многие люди говорят об IIS и ASP.NET, как будто они одно и то же, но на самом деле это два компонента, работающих вместе. Обратите внимание, что IIS 6 и 7 прослушивают порт завершения ввода-вывода, где они получают завершения из HTTP.sys. Для этого используется пул потоков IIS с максимальным числом потоков 256. Этот пул потоков спроектирован таким образом, что он плохо обрабатывает долго выполняющиеся задачи. Рекомендация группы IIS состоит в том, чтобы переключиться на другой поток, если вы собираетесь выполнять существенную работу, например, выполняемую обработчиком ASP.NET ISAPI и / или ASP.NET «интегрированный режим» в IIS 7. В противном случае вы будете связывать создание потоков IIS и предотвращение получения IIS завершений из HTTP.sys. Скорее всего, вас это не волнует, потому что вы не пишете собственный код, то есть вы не пишете ISAPI или собственный обработчик для IIS 7 конвейер. Вы, вероятно, просто используете ASP.NET, и в этом случае вас больше интересует его пул потоков и его работа.

В блоге http://blogs.msdn.com/tmarq/archive/2007/07/21/asp-net-thread-usage-on-iis-7-0-and-6-0.aspx есть сообщение, объясняющее, как ASP.NET использует потоки. Обратите внимание, что для ASP.NET v2.0 и v3.5 в IIS 7 следует увеличить MaxConcurrentRequestsPerCPU до 5000 - это ошибка, для которой по умолчанию на этих платформах было установлено значение 12. Новое значение по умолчанию для MaxConcurrentRequestsPerCPU в ASP.NET v4.0 на IIS 7 - 5000.

Чтобы ответить на три вопроса:

1) Сначала немного грунтовки. Только один поток на процессор может выполняться одновременно. Когда у вас есть больше, вы платите штраф - переключение контекста необходимо каждый раз, когда процессор переключается на другой поток, и это дорого. Однако, если поток заблокирован в ожидании работы ... тогда имеет смысл переключиться на другой поток, который может выполняться сейчас.

Так что, если у меня есть поток, который выполняет большую вычислительную работу и интенсивно использует процессор, а это занимает много времени, должен ли я переключиться на другой поток? Нет! Текущий поток эффективно использует процессор, поэтому переключение будет стоить только переключения контекста.

Так что, если у меня есть поток, который отправляет HTTP или SOAP-запрос на другой сервер и занимает много времени, должен ли я переключать потоки? Да! Вы можете выполнить HTTP или SOAP-запрос асинхронно, чтобы после «отправки» можно было размотать текущий поток и не использовать никакие потоки, пока не завершится ввод-вывод для «получения». Между «send» и «receive» удаленный сервер занят, поэтому локально вам не нужно блокировать поток, а вместо этого используйте асинхронные API, предоставляемые в .NET Framework, чтобы вы могли раскрутить и быть уведомленным после завершения.

Ладно, у вас вопросы №1: "Где находятся эти другие потоки? Есть ли другой пул потоков?" Это зависит Большая часть кода, работающего в .NET Framework, использует CLR ThreadPool, который состоит из двух типов потоков, рабочих потоков и потоков завершения ввода-вывода. А как насчет кода, который не использует CLR ThreadPool? Ну, он может создавать свои собственные потоки, использовать свой собственный пул потоков или что угодно, потому что у него есть доступ к API-интерфейсам Win32, предоставляемым операционной системой. Исходя из того, что мы обсуждали несколько лет назад, действительно не имеет значения, откуда берется поток, а поток - это поток, если речь идет об операционной системе и оборудовании.

2) Во втором вопросе вы утверждаете: «Я не вижу преимущества перемещения этого запроса в этот другой пул потоков». Вы правы, считая, что переключение НЕТ преимущества, если вы не собираетесь компенсировать это дорогостоящее переключение контекста, которое вы только что выполнили для переключения. Вот почему я привел пример медленного HTTP или SOAP-запроса к удаленному серверу как пример веской причины для переключения. И, кстати, ASP.NET не создает никаких потоков. Он использует CLR ThreadPool, а потоки в этом пуле полностью управляются CLR. Они довольно хорошо определяют, когда вам нужно больше потоков. Например, именно поэтому ASP.NET может легко масштабироваться от одновременного выполнения 1 запроса до одновременного выполнения 300 запросов без каких-либо действий. Входящие запросы публикуются в CLR ThreadPool через вызов QueueUserWorkItem, и CLR решает, когда вызывать WaitCallback (см. MSDN).

3) Третий вопрос: «Если основной поток передает запрос другому потоку, почему запрос не отключается?» Итак, IIS получает завершение ввода-вывода из HTTP.sys, когда запрос первоначально поступает на сервер. Затем IIS вызывает обработчик ASP.NET (или ISAPI). ASP.NET немедленно помещает запрос в очередь потоков CLR и возвращает ожидающий статус в IIS. Этот статус ожидания сообщает IIS, что мы еще не закончили, но как только мы закончим, мы сообщим вам. Теперь ASP.NET управляет жизнью этого запроса. Когда поток CLR ThreadPool вызывает ASP.NET WaitCallback (см. MSDN), он может выполнить весь запрос в этом потоке, что является нормальным случаем. Или он может переключиться на один или несколько других потоков, если запрос является тем, что мы называем асинхронным, т.е. у него есть асинхронный модуль или обработчик. В любом случае, есть четко определенные способы завершения запроса, и когда он, наконец, завершится, ASP.NET сообщит IIS, что мы закончили, и IIS отправит последние байты клиенту и закроет соединение, если Keep-Alive не используется.

С уважением, Томас

9 голосов
/ 05 апреля 2010

Асинхронные страницы в ASP.NET используют асинхронные обратные вызовы, а асинхронные обратные вызовы используют пул потоков, и он является таким же пулом потоков, используемым для обслуживания запросов ASP.NET.

Однако не все так просто. .NET ThreadPool имеет два типа потоков - рабочие потоки и потоки ввода / вывода. Потоки ввода / вывода используют так называемый порт завершения ввода / вывода , который (здесь очень упрощенно упрощен) - это не требующий обработки поток или независимый от потока способ ожидания завершения операции чтения / записи на дескрипторе файла для завершения , впоследствии запустив метод обратного вызова.

(Обратите внимание, что дескриптор файла не обязательно относится к файлу на диске; для Windows это также может быть сокет, канал и т. Д.)

Типичному веб-разработчику .NET на самом деле не нужно знать обо всем этом. Конечно, если вы писали реальный веб-сервер или любой другой сетевой сервер, то вам определенно нужно было бы узнать об этом, потому что они являются только способом обработки сотен входящих соединений без создания сотен потоков для их обслуживания. Если вам интересно, есть Порт завершения управляемого ввода / вывода (CodeProject).

Во всяком случае, возвращаясь к теме; когда вы взаимодействуете с пулом потоков на высоком уровне, то есть записываете:

ThreadPool.QueueUserWorkItem(s => DoSomeWork(s));

не использует порт завершения ввода / вывода. Когда-либо. Он отправляет работу в один из обычных рабочих потоков, управляемых пулом потоков. То же самое, если вы используете асинхронные обратные вызовы:

Func<int> asyncFunc;

IAsyncResult BeginOperation(object sender, EventArgs e, AsyncCallback cb,
    object state)
{
    asyncFunc = () => { Thread.Sleep(500); return 42; };
    return asyncFunc.BeginInvoke(cb, state);
}

void EndOperation(IAsyncResult ar)
{
    int result = asyncFunc.EndInvoke(ar);
    Console.WriteLine(result);
}

Снова - та же самая сделка. Внутри EndOperation вы работаете в ThreadPool рабочем потоке. Вы можете убедиться в этом, вставив следующий код отладки:

void EndSimpleWait(IAsyncResult ar)
{
    int maxWorkers, maxIO, availableWorkers, availableIO;
    ThreadPool.GetMaxThreads(out maxWorkers, out maxIO);
    ThreadPool.GetAvailableThreads(out availableWorkers, out availableIO);
    int result = asyncFunc.EndInvoke(ar);
}

Вставьте точку останова, и вы увидите, что availableWorkers на единицу меньше maxWorkers, тогда как maxIO и availableIO одинаковы.

Но некоторые асинхронные операции являются "особыми" в .NET. Это на самом деле не имеет ничего общего с ASP.NET - они также будут использовать порты завершения ввода / вывода в приложении Winforms или WPF. Примеры:

И так далее, это далеко не полный список. Практически каждый класс в .NET Framework, который предоставляет свои собственные методы BeginXYZ и EndXYZ и может предположительно выполнять любые операции ввода-вывода, вероятно, использует порты завершения ввода-вывода. Это поможет вам, разработчику приложений, потому что потоки ввода-вывода довольно сложно реализовать самостоятельно в .NET.

Я предполагаю, что разработчики .NET Framework сознательно решили затруднить публикацию операций ввода-вывода (по сравнению с рабочими потоками, где вы можете просто написать ThreadPool.QueueUserWorkItem), потому что это сравнительно "опасно", если вы этого не сделаете знать, как правильно их использовать; напротив, на самом деле довольно просто порождать их в Windows API .

Как и раньше, вы можете проверить, что происходит, с помощью некоторого кода отладки:

WebRequest request;

IAsyncResult BeginDownload(object sender, EventArgs e,
    AsyncCallback cb, object state)
{
    request = WebRequest.Create("http://www.example.com");
    return request.BeginGetResponse(cb, state);
}

void EndDownload(IAsyncResult ar)
{
    int maxWorkers, maxIO, availableWorkers, availableIO;
    ThreadPool.GetMaxThreads(out maxWorkers, out maxIO);
    ThreadPool.GetAvailableThreads(out availableWorkers, out availableIO);
    string html;
    using (WebResponse response = request.EndGetResponse(ar))
    {
        using (StreamReader reader = new
            StreamReader(response.GetResponseStream()))
        {
            html = reader.ReadToEnd();
        }
    }
}

Если вы пройдете через это, вы увидите, что статистика потоков отличается. availableWorkers будет соответствовать maxWorkers, но availableIO на единицу меньше maxIO. Это потому, что вы работаете в потоке ввода / вывода. Вот почему вы не должны выполнять дорогостоящие вычисления в асинхронных обратных вызовах - публикация ресурсоемкой работы на порте завершения ввода / вывода неэффективна и, что ж, плохо.

Все это объясняет, почему настоятельно рекомендуется использовать асинхронные страницы в ASP.NET, когда вам необходимо выполнить какие-либо операции ввода-вывода. Шаблон only полезен для I / O операций; асинхронные операции, не связанные с вводом-выводом, в конечном итоге будут опубликованы в рабочих потоках в ThreadPool, и вы все равно будете блокировать последующие запросы ASP.NET. Но вы можете создавать практически неограниченное количество асинхронных операций ввода-вывода и не задумываться над этим; они не будут использовать любые потоки вообще, пока не завершится ввод-вывод и не будет готов обратный вызов.

Итак, подведем итог - есть только один ThreadPool, но в нем есть разные виды потоков, и если вы выполняете медленные операции ввода-вывода, тогда это много более эффективно использовать потоки ввода / вывода. Это не имеет ничего общего с процессором или памятью, все связано с вводом-выводом и файловыми дескрипторами.


Что касается # 3, то на самом деле это не вопрос «почему не запрос отключается», больше похоже на вопрос «почему будет это?» Сокет не закрывается просто потому, что в данный момент нет потока, отправляющего или получающего данные из него, точно так же, как ваша входная дверь не закрывается автоматически, если там никого нет, чтобы приветствовать гостей. Клиентские операции могут истечь , если сервер не отвечает на них и может впоследствии отключиться от их конца, но это совсем другая проблема.

2 голосов
/ 05 апреля 2010

1) Потоки находятся в w3svc или любом другом процессе, выполняющем движок ASP.NET в вашей конкретной версии IIS.

2) Не уверен, что вы имеете в виду здесь. Вы фактически можете контролировать количество потоков в пуле рабочих потоков. Эта статья довольно хороша: http://msdn.microsoft.com/en-us/library/ms998549.aspx

3) Я думаю, что вы сбиваете с толку Запросы и соединения ... Честно говоря, я понятия не имею, как работают внутренние компоненты IIS, но обычно в приложениях, которые обрабатывают несколько запросов одновременно, существует ОДИН основной поток прослушивания, затем передайте реальную работу дочернему потоку (и больше ничего не делайте). Исходный запрос не «отключен», потому что эти вещи происходят на совершенно разных уровнях стека сетевых протоколов. Windows Server без проблем принимает несколько соединений через порт TCP 80. Подумайте о том, как работает TCP / IP, и о том, что он отправляет несколько отдельных пакетов информации. Вы думаете о «соединении», как об одном шланге, идущем от патрубка A к патрубку B, но, конечно, это не совсем так. Это больше похоже на ведро, которое просто собирает все, что попадает в него.

Надеюсь, это поможет.

0 голосов
/ 05 апреля 2010

Ответ также зависит от того, о какой версии IIS вы говорите. В более ранних версиях ASP.NET не использовал «потоки IIS». Это были темы .NET ThreadPool. В IIS 7 конвейеры IIS и ASP.NET были объединены. Я не знаю, какие потоки использует ASP.NET сейчас.

Суть в том, что не создавайте свои собственные темы.

...