Понимание ASP.NET асинхронная обработка

#asp.net #asynchronous #threadpool

#asp.net #асинхронный #пул потоков

Вопрос:

Каждая статья по асинхронному программированию в ASP.NET до сих пор я читал, что, когда мы делаем запрос к базе данных (или другому IO), поток запроса возвращается в ThreadPool (Страница.AddOnPreRenderCompleteAsync()). Насколько я понимаю, весь асинхронный ввод-вывод зависит от функции .NET, позволяющей нам вызывать любой делегат асинхронно. Но, согласно MSDN, вызов делегата с BeginInvoke()/EndInvoke() по — прежнему включает ThreadPool в себя . Это означает, что если мы вернули текущий http-поток ThreadPool (путем вызова Page.AddOnPreRenderCompleteAsync() ), нам нужен другой поток для выполнения нашего делегата. Какой в этом смысл?

Еще одна проблема, которую я не могу понять, — это то, что происходит, когда асинхронный вызов завершается и обработка запроса продолжается. Скорее всего, в другом потоке, верно? (поскольку исходный поток может быть повторно использован для других запросов). А это значит, что мы теряем .CurrentPrincipal .CurrentCulture и другой контекст потока. Так в чем же смысл, опять же?

Обновление MSDN : абсолютно ясно об использовании ThreadPool в стандартном BeginInvoke()/EndInvoke() шаблоне:

Передайте делегат для метода обратного вызова в BeginInvoke. Метод выполняется в потоке ThreadPool после завершения асинхронного вызова. Метод обратного вызова вызывает EndInvoke.

Это верно для .NET 1.1 — .NET 4.

Ответ №1:

В этой статье рассматриваются основы и есть загружаемый пример решения, с которым вы можете поиграть.

Комментарии:

1. Очень полезно, особенно метод, который позволяет «автоматически передавать олицетворение, культуру и HttpContext.Current в обработчик события завершения».

Ответ №2:

ОБНОВЛЕННЫЙ: Детали происходящего важны, но я начну с простого ответа. Ваш главный вопрос заключается в том, как вы решаете проблему с получением информации о локальном состоянии вашего потока? Если вы написали свои методы Begin и EndInvoke как методы в коде для page, у вас есть доступ к любому из элементов данных на странице. Поэтому просто создайте личный элемент данных IPrincipal principal_ и CultureInfo culture_ элементы данных в своем коде и инициализируйте их в своем методе page_load. Затем из ваших методов Begin и EndInvoke у вас будет прямой доступ к ним.

Теперь дьяволы в деталях, которые, надеюсь, отвечают на ваши вопросы «в чем смысл»: сначала я немного расскажу о пулах потоков, потому что я не думаю, что вам ясно, как потоки связаны с ними. Пул потоков может иметь 1 поток или N рабочих потоков, работающих над ним. Когда делегат добавляется в пул потоков, он обычно фактически не создает новые потоки. Что происходит поток, назначенный пулу, который уже ничего не делает, придет и выполнит некоторую работу … если поток завершится до того, как придет время для переключения контекста, он может выполнить другую задачу и запустить ее. Один поток в пуле потоков не ограничивается одной задачей. Поток может запускать несколько задач, которые могут вызывать несколько асинхронных обработчиков последовательно. Тот факт, что поток из пула потоков может вызывать асинхронный обработчик, не имеет значения. Таким образом, многие делегаты могут быть запущены с относительно небольшим количеством потоков и меньшим количеством переключений контекста. Когда пул потоков используется в асинхронном шаблоне, большую часть времени детали заданий запрашивают, завершилась ли операция блокировки, и, если она была, вызывают следующий обратный вызов для продолжения или завершения работы асинхронной операции. У вас могут быть тысячи обработчиков, блокирующих и опрашивающих, и только несколько потоков когда-либо заняты их обработкой, потому что большую часть времени только у немногих есть работа, которую нужно выполнить. Это эффективно, и в этом суть!

Важно отметить, что вызов метода асинхронного обработчика BeginInvoke / EndInvoke не означает, что он использует поток или включает пул потоков. Если ввод-вывод (или что бы это ни было) выполняется достаточно быстро, часто бывает, что в пул потоков вообще ничего не загружается. Реализация имеет возможность НИКОГДА не создавать поток или отправлять задание в пул потоков. Если он не выполняет этих действий, то асинхронный обработчик никогда не переключит контекст и вообще не будет выполняться параллельно с вызывающим потоком. Это нормально, асинхронные методы требуются только для выполнения как можно большего объема работы и требуются только для использования пула потоков, если ему приходится чего-то ждать. Асинхронный метод и обратные вызовы никогда не могут быть перенесены в отдельный поток. Это важное различие, которое обеспечивает эффективность таких ситуаций, как буферизованные асинхронные вызовы ввода-вывода, без необходимости переключения контекста.

Книга C # 4.0 в двух словах является ОТЛИЧНЫМ ресурсом и очень хорошо объясняет асинхронный шаблон и методологию.

Уточнение, которое вы делаете с помощью ссылки MSDN, вводит в заблуждение, потому что вы упустили ее полный контекст:

Примеры кода в этом разделе демонстрируют четыре распространенных способа использования BeginInvoke и EndInvoke для выполнения асинхронных вызовов. После вызова BeginInvoke вы можете выполнить следующие действия:

Проделайте некоторую работу, а затем вызовите EndInvoke для блокировки до завершения вызова.

Получить дескриптор ожидания с помощью системы.IAsyncResult.Свойство AsyncWaitHandle, используйте его метод WaitOne, чтобы блокировать выполнение до тех пор, пока не будет подан сигнал WaitHandle, а затем вызовите EndInvoke .

Опросите результат IAsyncResult, возвращаемый BeginInvoke, чтобы определить, когда асинхронный вызов завершен, а затем вызовите EndInvoke .

Передайте делегат для метода обратного вызова для BeginInvoke. Метод выполняется в потоке ThreadPool после завершения асинхронного вызова. Метод обратного вызова вызывает EndInvoke.

Как вы можете видеть, пункт, который вы указали, относится к примеру и одному варианту из нескольких, поэтому НЕ гарантируется, что какой-либо метод выполняется в пуле потоков. Также ваша ссылка на меня в комментариях объясняет ситуацию для AddOnPreRenderCompleteAsync, которая вызывает обработчик async в пуле потоков, но, опять же, это все еще ситуативно для их примера. (Статья)

Важно то, что вы сказали, что дважды проверили, что ваш метод (какой именно?) Вызывается в другом потоке. Вероятно, это ваш метод обратного вызова, который вы зарегистрировали с помощью другой асинхронной операции? Возможно, это было бы запущено в другом потоке, если бы вы вызвали BeginInvoke для другого асинхронного метода из ваших собственных обработчиков. До выполнения каких-либо подобных вызовов вы, скорее всего, все еще находитесь в потоке, связанном со страницей. Даже если вы выполнили вызов асинхронного метода, который реализует себя, помещая что-то в пул потоков в какой-то момент, если эта операция была достаточно быстрой, возможно, она действительно может пропустить этот шаг! Единственная причина, по которой я так долго объяснял тот же результат завершения в другом потоке, по другой причине, заключается в том, что вы понимаете, что поведение асинхронного вызова НЕ требует пула потоков, но вы не можете полагаться на переключение контекста или нет, и точка асинхронных вызовов непараллелизм, но эффективность.

Комментарии:

1. Я не упоминал ни задачи, ни PLinq, поскольку сначала хотел получить основы, просто стандартные средства, доступные в .NET 1.1 — 2.0, поэтому ваш ответ действительно делает вещи еще более сложными для меня: (

2. Что касается второй части — я нашел подтверждение того, что поток, который мы получаем обратно в PreRender(), на самом деле не тот ( msdn.microsoft.com/en-us/magazine/cc163463.aspx ) таким образом, контекст теряется. В любом случае, спасибо, 1 за ваши усилия.

3. Хорошо, вам нужно четко понимать, какие версии .NET вы ищете, о которых идет речь, чтобы кто-то другой не допустил этой ошибки.

4. Спасибо за обновление предварительной загрузки со статьей MSDN. Я уходил от документации API, которая была неоднозначной в данной ситуации. Я обновлю свой ответ.

Ответ №3:

А это значит , что мы проигрываем .CurrentPrincipal , .CurrentCulture и другой контекст потока. Так в чем же смысл, опять же?

Переключение контекста обходится дорого, но концепция в целом эффективна, пока общее количество потоков остается в пределах рекомендуемого порога.

Комментарии:

1. Если бы мы вернули поток запроса обратно в ThreadPool и получили другой (из ThreadPool) для обработки асинхронной работы? Таким образом, в каждый момент времени всегда есть поток, занятый работой, и мы получили 2 переключения контекста. Я что-то упускаю из виду?