После этого вопроса мне удобно при использовании асинхронных операций в ASP.NET MVC. Итак, я написал два сообщения в блоге об этом:
У меня слишком много недоразумений по поводу асинхронных операций в ASP.NET MVC.
Я всегда слышу это предложение: приложение может масштабироваться лучше, если операции выполняются асинхронно
И я тоже много слышал такого рода предложения: если у вас огромный объем трафика, вам может быть лучше не выполнять ваши запросы асинхронно - использование 2 дополнительных потоков для обслуживания одного запроса отнимает ресурсы у других входящих запросов.
Я думаю, что эти два предложения противоречивы.
У меня мало информации о том, как работает пул потоков в ASP.NET, но я знаю, что пул потоков имеет ограниченный размер для потоков. Итак, второе предложение должно быть связано с этим вопросом.
И я хотел бы знать, если асинхронные операции в ASP.NET MVC использует поток из ThreadPool на .NET 4?
Например, когда мы реализуем AsyncController, как работает структура приложения? Если я получаю огромный трафик, это хорошая идея для реализации AsyncController?
Есть ли кто-нибудь, кто может убрать этот черный занавес перед моими глазами и объяснить мне, что такое асинхронность в ASP.NET MVC 3 (NET 4)?
Редактировать:
Я прочитал этот документ почти сотни раз, и я понимаю основную сделку, но все же у меня возникает путаница, потому что слишком много противоречивых комментариев.
Использование асинхронного контроллера в ASP.NET MVC
Редактировать:
Давайте предположим, что у меня есть действие контроллера, как показано ниже (не реализация, AsyncController
хотя):
public ViewResult Index() {
Task.Factory.StartNew(() => {
//Do an advanced looging here which takes a while
});
return View();
}
Как вы видите здесь, я запускаю операцию и забываю об этом. Затем я возвращаюсь немедленно, не дожидаясь его завершения.
В этом случае нужно ли использовать поток из пула потоков? Если так, после того, как это завершится, что случится с этим потоком? Имеет ли GC
приходит и убирать только после его завершения?
Редактировать:
Для ответа @ Darin, вот пример асинхронного кода, который обращается к базе данных:
public class FooController : AsyncController {
//EF 4.2 DbContext instance
MyContext _context = new MyContext();
public void IndexAsync() {
AsyncManager.OutstandingOperations.Increment(3);
Task<IEnumerable<Foo>>.Factory.StartNew(() => {
return
_context.Foos;
}).ContinueWith(t => {
AsyncManager.Parameters["foos"] = t.Result;
AsyncManager.OutstandingOperations.Decrement();
});
Task<IEnumerable<Bars>>.Factory.StartNew(() => {
return
_context.Bars;
}).ContinueWith(t => {
AsyncManager.Parameters["bars"] = t.Result;
AsyncManager.OutstandingOperations.Decrement();
});
Task<IEnumerable<FooBar>>.Factory.StartNew(() => {
return
_context.FooBars;
}).ContinueWith(t => {
AsyncManager.Parameters["foobars"] = t.Result;
AsyncManager.OutstandingOperations.Decrement();
});
}
public ViewResult IndexCompleted(
IEnumerable<Foo> foos,
IEnumerable<Bar> bars,
IEnumerable<FooBar> foobars) {
//Do the regular stuff and return
}
}
источник
Ответы:
Вот отличная статья, которую я бы порекомендовал вам прочитать, чтобы лучше понять асинхронную обработку в ASP.NET (то, что в основном представляют асинхронные контроллеры).
Давайте сначала рассмотрим стандартное синхронное действие:
Когда делается запрос к этому действию, поток извлекается из пула потоков, и тело этого действия выполняется в этом потоке. Поэтому, если обработка внутри этого действия медленная, вы блокируете этот поток для всей обработки, поэтому этот поток нельзя использовать повторно для обработки других запросов. В конце выполнения запроса поток возвращается в пул потоков.
Теперь давайте возьмем пример асинхронного шаблона:
Когда запрос отправляется действию Index, поток извлекается из пула потоков и выполняется тело
IndexAsync
метода. После завершения выполнения тела этого метода поток возвращается в пул потоков. Затем, используя стандартAsyncManager.OutstandingOperations
, после того, как вы дадите сигнал о завершении асинхронной операции, из пула потоков будет извлечен другой поток, и телоIndexCompleted
действия будет выполнено, а результат будет представлен клиенту.Таким образом, в этом шаблоне мы видим, что один клиентский HTTP-запрос может выполняться двумя разными потоками.
Теперь интересная часть происходит внутри
IndexAsync
метода. Если у вас есть блокирующая операция внутри, вы полностью теряете всю цель асинхронных контроллеров, потому что блокируете рабочий поток (помните, что тело этого действия выполняется в потоке, извлеченном из пула потоков).Итак, когда мы сможем воспользоваться реальными преимуществами асинхронных контроллеров?
ИМХО, мы можем получить больше всего, когда у нас интенсивные операции ввода / вывода (например, вызовы базы данных и сетевые вызовы удаленных служб). Если у вас интенсивная загрузка процессора, асинхронные действия не принесут вам большой пользы.
Так почему же мы можем извлечь выгоду из интенсивных операций ввода-вывода? Потому что мы могли бы использовать порты завершения ввода / вывода . IOCP чрезвычайно мощны, потому что вы не потребляете никаких потоков или ресурсов на сервере во время выполнения всей операции.
Как они работают?
Предположим, что мы хотим загрузить содержимое удаленной веб-страницы, используя метод WebClient.DownloadStringAsync . Вы вызываете этот метод, который регистрирует IOCP в операционной системе и немедленно возвращает его. Во время обработки всего запроса потоки на вашем сервере не используются. Все происходит на удаленном сервере. Это может занять много времени, но вас это не волнует, так как вы не подвергаете опасности свои рабочие потоки. Как только ответ получен, IOCP сигнализируется, поток извлекается из пула потоков, и в этом потоке выполняется обратный вызов. Но, как вы видите, в течение всего процесса мы не монополизировали никакие потоки.
То же самое относится и к таким методам, как FileStream.BeginRead, SqlCommand.BeginExecute, ...
Как насчет распараллеливания нескольких вызовов базы данных? Предположим, что у вас было синхронное действие контроллера, в котором вы последовательно выполнили 4 блокировки базы данных. Нетрудно подсчитать, что если каждый вызов базы данных занимает 200 мс, выполнение вашего контроллера займет примерно 800 мс.
Если вам не нужно выполнять эти вызовы последовательно, улучшится ли их распараллеливание?
Это большой вопрос, на который нелегко ответить. Может быть да, а может быть и нет. Это будет полностью зависеть от того, как вы реализуете эти вызовы базы данных. Если вы используете асинхронные контроллеры и порты завершения ввода / вывода, как обсуждалось ранее, вы повысите производительность этого действия контроллера, а также других действий, поскольку вы не будете монополизировать рабочие потоки.
С другой стороны, если вы реализуете их плохо (с блокирующим вызовом базы данных, выполненным для потока из пула потоков), вы в основном снизите общее время выполнения этого действия примерно до 200 мс, но вы бы использовали 4 рабочих потока, чтобы вы могло ухудшить производительность других запросов, которые могли бы стать голодными из-за отсутствия потоков в пуле для их обработки.
Так что это очень сложно, и если вы не чувствуете, что готовы выполнить обширные тесты для своего приложения, не устанавливайте асинхронные контроллеры, так как есть вероятность, что вы нанесете больше вреда, чем пользы. Реализуйте их только в том случае, если у вас есть для этого причина: например, вы определили, что стандартные действия синхронного контроллера являются узким местом для вашего приложения (после проведения обширных нагрузочных тестов и измерений, разумеется).
Теперь давайте рассмотрим ваш пример:
Когда получен запрос на действие Index, из пула потоков извлекается поток для выполнения его тела, но его тело только планирует новую задачу, используя TPL . Таким образом, выполнение действия заканчивается, и поток возвращается в пул потоков. Кроме того, TPL использует потоки из пула потоков для выполнения их обработки. Таким образом, даже если исходный поток был возвращен в пул потоков, вы извлекли другой поток из этого пула, чтобы выполнить тело задачи. Итак, вы поставили под угрозу 2 потока из вашего драгоценного пула.
Теперь давайте рассмотрим следующее:
В этом случае мы вручную создаем поток. В этом случае выполнение тела действия Index может занять немного больше времени (поскольку порождение нового потока обходится дороже, чем извлечение потока из существующего пула). Но выполнение расширенной операции регистрации будет выполняться в потоке, который не является частью пула. Таким образом, мы не подвергаем опасности потоки из пула, которые остаются свободными для обслуживания других запросов.
источник
System.Threading.Task
), работающих внутриIndexAsync
метода. Внутри этих операций мы делаем вызовы БД на сервер. Итак, все они являются интенсивными операциями ввода-вывода, верно? В этом случае мы создаем 4 отдельных потока (или получаем 4 отдельных потока из пула потоков)? Предполагая, что у меня есть многоядерный компьютер, они также будут работать параллельно, верно?SqlCommand.ExecuteReader
вы теряете все, так как это блокирующий вызов. Вы блокируете поток, в котором выполняется этот вызов, и если этот поток является потоком из пула, это очень плохо. Вы будете пользоваться только при использовании портов ввода / вывода завершения:SqlCommand.BeginExecuteReader
. Если вы не используете IOCP независимо от того, что вы делаете, не используйте асинхронные контроллеры, так как вы нанесете больше вреда, чем пользы для общей производительности вашего приложения._context.Foo
вы на самом деле ничего не выполняете. Вы просто строите дерево выражений. Будьте предельно осторожны с этим. Выполнение запроса откладывается только тогда, когда вы начинаете перечисление по набору результатов. И если это происходит в представлении, это может иметь катастрофические последствия для производительности. Чтобы с готовностью выполнить запрос EF, добавьте.ToList()
в конце.Да - все потоки происходят из пула потоков. Ваше приложение MVC уже является многопоточным, когда запрос приходит в новом потоке, он будет взят из пула и использован для обслуживания запроса. Этот поток будет «заблокирован» (от других запросов) до тех пор, пока запрос не будет полностью обслужен и завершен. Если в пуле нет доступных потоков, запрос должен будет ждать, пока один из них не станет доступным.
Если у вас есть асинхронные контроллеры, они все еще получают поток из пула, но при обслуживании запроса они могут отказаться от потока, ожидая, когда что-то произойдет (и этот поток может быть передан другому запросу), и когда исходный запрос нуждается в потоке. снова он получает один из бассейна.
Разница в том, что если у вас много длительных запросов (где поток ожидает ответа от чего-либо), у вас могут закончиться потоки из пула для обслуживания даже базовых запросов. Если у вас есть асинхронные контроллеры, у вас больше нет потоков, но те потоки, которые ожидают, возвращаются в пул и могут обслуживать другие запросы.
Почти реальный пример из жизни ... Подумайте об этом , как получить на автобусе, есть пяти людей , которые ждут , чтобы попасть на первом уживается, платит и садится (водитель обслуживает их запрос), вы получите на (драйвер обслуживание ваш запрос) но вы не можете найти свои деньги; когда вы копаетесь в карманах, водитель разочаровывается в вас и заставляет следующих двух человек (обслуживать их запросы), когда вы находите свои деньги, водитель снова начинает общаться с вами (завершает ваш запрос) - пятый должен ждать, пока Вы закончили, но третий и четвертый люди были обслужены, в то время как вы были на полпути, когда вас обслужили. Это означает, что водитель является единственным потоком из пула, а пассажиры - запросами. Было бы слишком сложно написать, как это будет работать, если бы было два драйвера, но вы можете себе представить ...
Без асинхронного контроллера пассажирам позади вас пришлось бы ждать целую вечность, пока вы искали свои деньги, в то время как водитель автобуса не работал бы.
Таким образом, вывод таков: если многие люди не знают, где находятся их деньги (то есть требуется много времени, чтобы ответить на то, что попросил водитель), асинхронные контроллеры вполне могут помочь пропускной способности запросов, ускоряя процесс от некоторых. Без контроллера aysnc все ждут, пока с лицом, стоящим впереди, не будут полностью разбираться. НО не забывайте, что в MVC на одной шине много драйверов, поэтому асинхронность не является автоматическим выбором.
источник
Здесь действуют две концепции. Прежде всего, мы можем заставить наш код работать параллельно, чтобы он выполнялся быстрее, или планировать код в другом потоке, чтобы не заставлять пользователя ждать. Пример, который вы имели
принадлежит ко второй категории. Пользователь получит более быстрый ответ, но общая нагрузка на сервер будет выше, поскольку он должен выполнять ту же работу + обрабатывать потоки.
Другой пример этого будет:
Поскольку запросы выполняются параллельно, пользователю не придется ждать так же долго, как если бы они выполнялись последовательно. Но вы должны понимать, что мы используем здесь больше ресурсов, чем если бы мы работали в последовательном режиме, потому что мы выполняем код во многих потоках, в то время как у нас тоже есть ожидание.
Это прекрасно в клиентском сценарии. И это довольно распространено, чтобы оборачивать синхронный длинный исполняемый код в новое задание (запускать его в другом потоке), чтобы пользовательский интерфейс реагировал или парализовался, чтобы сделать его быстрее. Тем не менее, поток все еще используется в течение всей продолжительности. На сервере с высокой нагрузкой это может иметь неприятные последствия, поскольку вы фактически используете больше ресурсов. Это то, о чем вас предупреждали
У асинхронных контроллеров в MVC есть и другая цель. Суть в том, чтобы избежать того, что потоки сидят без дела (что может повредить масштабируемости). Это действительно имеет значение, только если API, который вы вызываете, имеют асинхронные методы. Как и WebClient.DowloadStringAsync ().
Дело в том, что вы можете позволить вашему потоку возвращаться для обработки новых запросов до тех пор, пока веб-запрос не будет завершен, где он будет вызывать вас с помощью обратного вызова, который получит тот же или новый поток и завершит запрос.
Я надеюсь, вы понимаете разницу между асинхронным и параллельным. Думайте о параллельном коде как о коде, где ваш поток сидит и ждет результата. В то время как асинхронный код - это код, в котором вы будете уведомлены о завершении работы кода и сможете вернуться к работе с ним, тем временем поток может выполнять другую работу.
источник
Приложения могут лучше масштабироваться, если операции выполняются асинхронно, но только при наличии ресурсов, доступных для обслуживания дополнительных операций .
Асинхронные операции гарантируют, что вы никогда не блокируете действие, потому что уже выполняется существующее. ASP.NET имеет асинхронную модель, которая позволяет одновременно выполнять несколько запросов. Можно было бы поставить запросы в очередь и обработать их FIFO, но это не будет хорошо масштабироваться, когда в очереди находятся сотни запросов, а для обработки каждого запроса требуется 100 мс.
Если у вас огромный объем трафика, вам может быть лучше не выполнять запросы асинхронно, так как может не быть дополнительных ресурсов для обслуживания запросов . Если нет свободных ресурсов, ваши запросы будут вынуждены стоять в очереди, экспоненциально дольше или сразу же потерпеть неудачу, и в этом случае асинхронные издержки (мьютексы и операции переключения контекста) ничего вам не дают.
Что касается ASP.NET, у вас нет выбора - он использует асинхронную модель, потому что именно это имеет смысл для модели сервер-клиент. Если бы вы писали собственный код для внутреннего использования, который использует асинхронный шаблон, чтобы попытаться улучшить масштабирование, если вы не пытаетесь управлять ресурсом, который совместно используется всеми запросами, вы не увидите никаких улучшений, потому что они уже упакованы. в асинхронном процессе, который больше ничего не блокирует.
В конечном счете, все это субъективно, пока вы действительно не посмотрите на то, что вызывает узкое место в вашей системе. Иногда очевидно, где асинхронный шаблон поможет (предотвращая блокировку ресурсов в очереди). В конечном счете, только измерение и анализ системы могут указать, где вы можете повысить эффективность.
Редактировать:
В вашем примере
Task.Factory.StartNew
вызов поставит в очередь операцию в пуле потоков .NET. Природа потоков пула потоков должна использоваться повторно (чтобы избежать затрат на создание / уничтожение большого количества потоков). После завершения операции поток освобождается обратно в пул для повторного использования другим запросом (сборщик мусора фактически не участвует, если вы не создали некоторые объекты в своих операциях, и в этом случае они собираются как обычно обзорный).Что касается ASP.NET, то здесь нет специальной операции. Запрос ASP.NET завершается без учета асинхронной задачи. Единственное беспокойство может возникнуть, если ваш пул потоков насыщен (т.е. нет потоков, доступных для обслуживания запроса прямо сейчас, и настройки пула не позволяют создавать больше потоков), и в этом случае запрос блокируется в ожидании запуска задача, пока поток пула не станет доступным.
источник
Task.Factory.StartNew
вызов поставит в очередь операцию в пуле потоков .NET. , В этом контексте, что является правильным здесь: 1-) Он создает новый поток, и когда это будет сделано, этот поток возвращается в пул потоков и ожидает там повторного использования. 2-) Он получает поток из пула потоков, и этот поток возвращается в пул потоков и ожидает там повторного использования. 3-) Он принимает наиболее эффективный подход и может сделать любой из них.Да, они используют поток из пула потоков. На самом деле есть очень хорошее руководство от MSDN, которое ответит на все ваши вопросы и многое другое. Я нашел это весьма полезным в прошлом. Проверьте это!
http://msdn.microsoft.com/en-us/library/ee728598.aspx
Между тем, комментарии и предложения, которые вы слышите об асинхронном коде, следует воспринимать с недоверием. Для начала, просто сделать что-то асинхронным не обязательно сделает его лучше масштабируемым, а в некоторых случаях может ухудшить масштаб вашего приложения. Другой комментарий, который вы разместили о «огромном объеме трафика ...», также верен только в определенных контекстах. Это действительно зависит от того, что делают ваши операции и как они взаимодействуют с другими частями системы.
Короче говоря, многие люди имеют много мнений об асинхронности, но они могут быть неверными вне контекста. Я бы сказал, сосредоточьтесь на ваших точных проблемах и проведите базовое тестирование производительности, чтобы увидеть, какие асинхронные контроллеры и т. Д. Действительно обрабатывают ваше приложение.
источник
GC
приходит и очищает их сразу после того, как я закончу, чтобы у моего приложения не было утечки памяти, и так далее, и так далее. Любая идея на эту часть.Во-первых, это не MVC, а IIS, который поддерживает пул потоков. Таким образом, любой запрос к приложению MVC или ASP.NET обслуживается из потоков, которые поддерживаются в пуле потоков. Только выполнив приложение Asynch, он вызывает это действие в другом потоке и немедленно освобождает поток, чтобы можно было выполнить другие запросы.
Я объяснил то же самое с подробным видео ( http://www.youtube.com/watch?v=wvg13n5V0V0/ «Асинхронные контроллеры MVC и голодание потоков»), в котором показано, как происходит голодание потоков в MVC и как его свести к минимуму с помощью MVC Асинхронные контроллеры. Я также измерил очереди запросов с помощью perfmon, чтобы вы могли видеть, как уменьшаются очереди запросов для асинхронного MVC и насколько он хуже для операций синхронизации.
источник