Асинхронные страницы в 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, то на самом деле это не вопрос «почему не запрос отключается», больше похоже на вопрос «почему будет это?» Сокет не закрывается просто потому, что в данный момент нет потока, отправляющего или получающего данные из него, точно так же, как ваша входная дверь не закрывается автоматически, если там никого нет, чтобы приветствовать гостей. Клиентские операции могут истечь , если сервер не отвечает на них и может впоследствии отключиться от их конца, но это совсем другая проблема.