Вот отличная статья 1002 * Я бы порекомендовал вам прочитать, чтобы лучше понять асинхронную обработку в ASP.NET (именно это в основном представляют асинхронные контроллеры).
Давайте сначала рассмотрим стандартное синхронное действие:
public ActionResult Index()
{
// some processing
return View();
}
Когда делается запрос на это действие, поток извлекается из пула потоков, и тело этого действия выполняется в этом потоке. Поэтому, если обработка внутри этого действия медленная, вы блокируете этот поток для всей обработки, поэтому этот поток нельзя использовать повторно для обработки других запросов. В конце выполнения запроса поток возвращается в пул потоков.
Теперь давайте рассмотрим пример асинхронного шаблона:
public void IndexAsync()
{
// perform some processing
}
public ActionResult IndexCompleted(object result)
{
return View();
}
Когда запрос отправляется действию Index, поток извлекается из пула потоков и выполняется тело метода IndexAsync
. По завершении выполнения тела этого метода поток возвращается в пул потоков. Затем, используя стандартный AsyncManager.OutstandingOperations
, когда вы сообщаете о завершении асинхронной операции, из пула потоков извлекается другой поток, и тело действия IndexCompleted
выполняется над ним, а результат обрабатывается клиентом.
Итак, в этом шаблоне мы видим, что один клиентский HTTP-запрос может быть выполнен двумя разными потоками.
Теперь интересная часть происходит внутри метода IndexAsync
. Если у вас есть блокирующая операция внутри, вы полностью теряете цели асинхронных контроллеров, потому что блокируете рабочий поток (помните, что тело этого действия выполняется в потоке, взятом из пула потоков).
Так когда же мы сможем воспользоваться реальными преимуществами асинхронных контроллеров?
ИМХО, мы можем получить больше всего, когда у нас интенсивные операции ввода / вывода (например, вызовы базы данных и сетевые вызовы удаленных служб). Если у вас интенсивная загрузка процессора, асинхронные действия не принесут вам большой пользы.
Так почему же мы можем получить выгоду от интенсивных операций ввода-вывода? Потому что мы могли бы использовать I / O Completion Ports . IOCP чрезвычайно мощны, потому что вы не потребляете никаких потоков или ресурсов на сервере во время выполнения всей операции.
Как они работают?
Предположим, что мы хотим загрузить содержимое удаленной веб-страницы, используя метод WebClient.DownloadStringAsync . Вы вызываете этот метод, который регистрирует IOCP в операционной системе и немедленно возвращает его. Во время обработки всего запроса потоки на вашем сервере не используются. Все происходит на удаленном сервере. Это может занять много времени, но вас это не волнует, так как вы не подвергаете опасности свои рабочие потоки. Как только ответ получен, IOCP сигнализируется, поток извлекается из пула потоков, и в этом потоке выполняется обратный вызов. Но, как вы видите, в течение всего процесса мы не монополизировали никакие потоки.
То же самое относится и к таким методам, как FileStream.BeginRead, SqlCommand.BeginExecute, ...
Как насчет распараллеливания нескольких вызовов базы данных? Предположим, что у вас было синхронное действие контроллера, в котором вы последовательно выполнили 4 блокировки базы данных. Нетрудно подсчитать, что если каждый вызов базы данных занимает 200 мс, выполнение вашего контроллера займет примерно 800 мс.
Если вам не нужно выполнять эти вызовы последовательно, улучшит ли их распараллеливание?
Это большой вопрос, на который нелегко ответить. Может быть да, может быть нет. Это будет полностью зависеть от того, как вы реализуете эти вызовы базы данных. Если вы используете асинхронные контроллеры и порты завершения ввода / вывода, как обсуждалось ранее, вы повысите производительность этого действия контроллера, а также других действий, поскольку вы не будете монополизировать рабочие потоки.
С другой стороны, если вы плохо их реализуете (с блокирующим вызовом базы данных, выполненным в потоке из пула потоков), вы в основном снизите общее время выполнения этого действия примерно до 200 мс, но вы бы потратили 4 рабочих.потоки, так что вы могли снизить производительность других запросов, которые могут истощаться из-за отсутствия потоков в пуле для их обработки.
Так что это очень сложно, и если вы не готовы выполнить обширные тесты наВ вашем приложении не следует реализовывать асинхронные контроллеры, так как есть вероятность, что вы нанесете больше вреда, чем пользы.Реализуйте их только в том случае, если у вас есть для этого причина: например, вы определили, что стандартные действия синхронного контроллера являются узким местом для вашего приложения (после выполнения обширных нагрузочных тестов и измерений, разумеется).
Теперь давайте рассмотримпример:
public ViewResult Index() {
Task.Factory.StartNew(() => {
//Do an advanced looging here which takes a while
});
return View();
}
Когда получен запрос для действия Index, из пула потоков извлекается поток для выполнения его тела, но его тело только планирует новую задачу, используя TPL .Таким образом, выполнение действия заканчивается, и поток возвращается в пул потоков.Кроме того, TPL использует потоки из пула потоков для выполнения их обработки.Таким образом, даже если исходный поток был возвращен в пул потоков, вы извлекли другой поток из этого пула, чтобы выполнить тело задачи.Итак, вы поставили под угрозу 2 потока из вашего драгоценного пула.
Теперь давайте рассмотрим следующее:
public ViewResult Index() {
new Thread(() => {
//Do an advanced looging here which takes a while
}).Start();
return View();
}
В этом случае мы вручную создаем поток.В этом случае выполнение тела действия Index может занять немного больше времени (поскольку порождение нового потока обходится дороже, чем извлечение потока из существующего пула).Но выполнение расширенной операции ведения журнала будет выполняться в потоке, который не является частью пула.Таким образом, мы не подвергаем опасности потоки из пула, которые остаются свободными для обслуживания других запросов.