Понимание ASP.NET асинхронной обработки - PullRequest
1 голос
/ 26 октября 2011

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

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

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

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

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

Ответы [ 3 ]

2 голосов
/ 26 октября 2011

Эта статья охватывает основы и предлагает загружаемый пример решения, с которым можно поиграть.

1 голос
/ 26 октября 2011

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

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

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

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

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

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

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

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

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

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

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

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

0 голосов
/ 26 октября 2011

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

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

...