При выполнении асинхронных операций в ASP.NET MVC используйте поток из ThreadPool в .NET 4

158

После этого вопроса мне удобно при использовании асинхронных операций в 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

    }
}
tugberk
источник
Не уверен в ответе, но стоит отметить, что асинхронность и многопоточность - это разные вещи. Так что было бы возможно иметь фиксированное количество потоков с асинхронной обработкой. Что произойдет, если одна страница будет заблокирована, скажем, I / O, другая страница получит возможность работать в том же потоке. Вот как оба эти утверждения могут быть верными, async может сделать вещи быстрее, но слишком много потоков - проблема.
Крис Чилверс
@ChrisChilvers Да, многопоточность не всегда необходима при асинхронной работе. Я уже понял это, но я думаю, что у меня нет контроллера, насколько я могу судить. AsyncController подсчитывает, сколько потоков он хочет с моей точки зрения, но также не уверен в этом. Есть ли понятие потоков пула в настольных приложениях, таких как WPF? Я думаю, что количество потоков не является проблемой для таких приложений.
Tugberk
6
Взгляните на видео Threading with Jeff Richter
oleksii
Я думаю, что проблема (и, следовательно, несогласованность) заключается в том, что второе утверждение использует асинхронный, когда оно означает много потоков. Это может быть связано с тем, что именно так asp.net реализовал асинхронные страницы, и, следовательно, конкретная реализация запутала проблему (поскольку имя функции, вызывающей проблему, будет асинхронными страницами), но я не уверен насчет конкретной реализации. , Поэтому либо они означают «много потоков», либо они означают «асинхронные страницы в asp.net версии X», поскольку будущие версии могут изменить реализацию. Или они просто подразумевают использование пула потоков для выполнения асинхронных операций на странице.
Крис Чилверс
@ChrisChilvers о, мужик! Я более запутался после этих комментариев: s
tugberk

Ответы:

177

Вот отличная статья, которую я бы порекомендовал вам прочитать, чтобы лучше понять асинхронную обработку в 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метода. Если у вас есть блокирующая операция внутри, вы полностью теряете всю цель асинхронных контроллеров, потому что блокируете рабочий поток (помните, что тело этого действия выполняется в потоке, извлеченном из пула потоков).

Итак, когда мы сможем воспользоваться реальными преимуществами асинхронных контроллеров?

ИМХО, мы можем получить больше всего, когда у нас интенсивные операции ввода / вывода (например, вызовы базы данных и сетевые вызовы удаленных служб). Если у вас интенсивная загрузка процессора, асинхронные действия не принесут вам большой пользы.

Так почему же мы можем извлечь выгоду из интенсивных операций ввода-вывода? Потому что мы могли бы использовать порты завершения ввода / вывода . 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 может занять немного больше времени (поскольку порождение нового потока обходится дороже, чем извлечение потока из существующего пула). Но выполнение расширенной операции регистрации будет выполняться в потоке, который не является частью пула. Таким образом, мы не подвергаем опасности потоки из пула, которые остаются свободными для обслуживания других запросов.

Дарин димитров
источник
1
Действительно подробно, спасибо! Давайте предположим, что у нас есть 4 асинхронных Tasks ( System.Threading.Task), работающих внутри IndexAsyncметода. Внутри этих операций мы делаем вызовы БД на сервер. Итак, все они являются интенсивными операциями ввода-вывода, верно? В этом случае мы создаем 4 отдельных потока (или получаем 4 отдельных потока из пула потоков)? Предполагая, что у меня есть многоядерный компьютер, они также будут работать параллельно, верно?
tugberk
10
@tugberk, вызовы базы данных - это операции ввода-вывода, но все будет зависеть от того, как вы их реализуете. Если вы используете блокирующий вызов базы данных, например, SqlCommand.ExecuteReaderвы теряете все, так как это блокирующий вызов. Вы блокируете поток, в котором выполняется этот вызов, и если этот поток является потоком из пула, это очень плохо. Вы будете пользоваться только при использовании портов ввода / вывода завершения: SqlCommand.BeginExecuteReader. Если вы не используете IOCP независимо от того, что вы делаете, не используйте асинхронные контроллеры, так как вы нанесете больше вреда, чем пользы для общей производительности вашего приложения.
Дарин Димитров
1
Ну, большую часть времени я сначала использую код EF. Я не уверен, подходит ли он. Я выставил образец, который показывает, что я обычно делаю. Я обновил вопрос, вы можете посмотреть?
Tugberk
3
@tugberk, вы запускаете их параллельно, поэтому общее время выполнения меньше по сравнению с последовательным выполнением. Но для их запуска вы используете рабочие потоки. Ну, на самом деле EF ленив, поэтому, когда вы делаете, _context.Fooвы на самом деле ничего не выполняете. Вы просто строите дерево выражений. Будьте предельно осторожны с этим. Выполнение запроса откладывается только тогда, когда вы начинаете перечисление по набору результатов. И если это происходит в представлении, это может иметь катастрофические последствия для производительности. Чтобы с готовностью выполнить запрос EF, добавьте .ToList()в конце.
Дарин Димитров
2
@tugberk, вам понадобится инструмент нагрузочного тестирования, чтобы параллельно моделировать нескольких пользователей на вашем веб-сайте, чтобы увидеть, как он ведет себя при большой нагрузке. Mini Profiler не может имитировать нагрузку на ваш сайт. Это может помочь вам увидеть и оптимизировать ваши запросы ADO.NET и профилировать один запрос, который бесполезен, когда вам нужно увидеть, как ваш сайт ведет себя в реальной ситуации, когда многие пользователи обращаются к нему.
Дарин Димитров
49

Да - все потоки происходят из пула потоков. Ваше приложение MVC уже является многопоточным, когда запрос приходит в новом потоке, он будет взят из пула и использован для обслуживания запроса. Этот поток будет «заблокирован» (от других запросов) до тех пор, пока запрос не будет полностью обслужен и завершен. Если в пуле нет доступных потоков, запрос должен будет ждать, пока один из них не станет доступным.

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

Разница в том, что если у вас много длительных запросов (где поток ожидает ответа от чего-либо), у вас могут закончиться потоки из пула для обслуживания даже базовых запросов. Если у вас есть асинхронные контроллеры, у вас больше нет потоков, но те потоки, которые ожидают, возвращаются в пул и могут обслуживать другие запросы.

Почти реальный пример из жизни ... Подумайте об этом , как получить на автобусе, есть пяти людей , которые ждут , чтобы попасть на первом уживается, платит и садится (водитель обслуживает их запрос), вы получите на (драйвер обслуживание ваш запрос) но вы не можете найти свои деньги; когда вы копаетесь в карманах, водитель разочаровывается в вас и заставляет следующих двух человек (обслуживать их запросы), когда вы находите свои деньги, водитель снова начинает общаться с вами (завершает ваш запрос) - пятый должен ждать, пока Вы закончили, но третий и четвертый люди были обслужены, в то время как вы были на полпути, когда вас обслужили. Это означает, что водитель является единственным потоком из пула, а пассажиры - запросами. Было бы слишком сложно написать, как это будет работать, если бы было два драйвера, но вы можете себе представить ...

Без асинхронного контроллера пассажирам позади вас пришлось бы ждать целую вечность, пока вы искали свои деньги, в то время как водитель автобуса не работал бы.

Таким образом, вывод таков: если многие люди не знают, где находятся их деньги (то есть требуется много времени, чтобы ответить на то, что попросил водитель), асинхронные контроллеры вполне могут помочь пропускной способности запросов, ускоряя процесс от некоторых. Без контроллера aysnc все ждут, пока с лицом, стоящим впереди, не будут полностью разбираться. НО не забывайте, что в MVC на одной шине много драйверов, поэтому асинхронность не является автоматическим выбором.

К. Боб
источник
8
Очень хорошая аналогия. Спасибо.
Питсбург DBA
Мне понравилось описание. Спасибо
Омер Чансизоглу
Отличный способ объяснить это. Спасибо,
Ананд Вьяс
Ваш ответ в сочетании с ответом Дарина суммирует весь механизм асинхронных контроллеров, что это такое, и что более важно, чем это не является!
Нирман
Это хорошая аналогия, но мой единственный вопрос заключается в следующем: парень, который шарит в своих карманах по вашей аналогии, будет чем-то вроде работы / обработки в нашем приложении ... так какие процессы будут работать после того, как мы выпустим поток? Surly это другая нить? Итак, что мы получаем здесь?
Томуке,
10

Здесь действуют две концепции. Прежде всего, мы можем заставить наш код работать параллельно, чтобы он выполнялся быстрее, или планировать код в другом потоке, чтобы не заставлять пользователя ждать. Пример, который вы имели

public ViewResult Index() { 

    Task.Factory.StartNew(() => { 
        //Do an advanced looging here which takes a while
    });

    return View();
}

принадлежит ко второй категории. Пользователь получит более быстрый ответ, но общая нагрузка на сервер будет выше, поскольку он должен выполнять ту же работу + обрабатывать потоки.

Другой пример этого будет:

public ViewResult Index() { 

    Task.Factory.StartNew(() => { 
        //Make async web request to twitter with WebClient.DownloadString()
    });

    Task.Factory.StartNew(() => { 
        //Make async web request to facebook with WebClient.DownloadString()
    });


    //wait for both to be ready and merge the results

    return View();
}

Поскольку запросы выполняются параллельно, пользователю не придется ждать так же долго, как если бы они выполнялись последовательно. Но вы должны понимать, что мы используем здесь больше ресурсов, чем если бы мы работали в последовательном режиме, потому что мы выполняем код во многих потоках, в то время как у нас тоже есть ожидание.

Это прекрасно в клиентском сценарии. И это довольно распространено, чтобы оборачивать синхронный длинный исполняемый код в новое задание (запускать его в другом потоке), чтобы пользовательский интерфейс реагировал или парализовался, чтобы сделать его быстрее. Тем не менее, поток все еще используется в течение всей продолжительности. На сервере с высокой нагрузкой это может иметь неприятные последствия, поскольку вы фактически используете больше ресурсов. Это то, о чем вас предупреждали

У асинхронных контроллеров в MVC есть и другая цель. Суть в том, чтобы избежать того, что потоки сидят без дела (что может повредить масштабируемости). Это действительно имеет значение, только если API, который вы вызываете, имеют асинхронные методы. Как и WebClient.DowloadStringAsync ().

Дело в том, что вы можете позволить вашему потоку возвращаться для обработки новых запросов до тех пор, пока веб-запрос не будет завершен, где он будет вызывать вас с помощью обратного вызова, который получит тот же или новый поток и завершит запрос.

Я надеюсь, вы понимаете разницу между асинхронным и параллельным. Думайте о параллельном коде как о коде, где ваш поток сидит и ждет результата. В то время как асинхронный код - это код, в котором вы будете уведомлены о завершении работы кода и сможете вернуться к работе с ним, тем временем поток может выполнять другую работу.

Микаэль Элиассон
источник
6

Приложения могут лучше масштабироваться, если операции выполняются асинхронно, но только при наличии ресурсов, доступных для обслуживания дополнительных операций .

Асинхронные операции гарантируют, что вы никогда не блокируете действие, потому что уже выполняется существующее. ASP.NET имеет асинхронную модель, которая позволяет одновременно выполнять несколько запросов. Можно было бы поставить запросы в очередь и обработать их FIFO, но это не будет хорошо масштабироваться, когда в очереди находятся сотни запросов, а для обработки каждого запроса требуется 100 мс.

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

Что касается ASP.NET, у вас нет выбора - он использует асинхронную модель, потому что именно это имеет смысл для модели сервер-клиент. Если бы вы писали собственный код для внутреннего использования, который использует асинхронный шаблон, чтобы попытаться улучшить масштабирование, если вы не пытаетесь управлять ресурсом, который совместно используется всеми запросами, вы не увидите никаких улучшений, потому что они уже упакованы. в асинхронном процессе, который больше ничего не блокирует.

В конечном счете, все это субъективно, пока вы действительно не посмотрите на то, что вызывает узкое место в вашей системе. Иногда очевидно, где асинхронный шаблон поможет (предотвращая блокировку ресурсов в очереди). В конечном счете, только измерение и анализ системы могут указать, где вы можете повысить эффективность.

Редактировать:

В вашем примере Task.Factory.StartNewвызов поставит в очередь операцию в пуле потоков .NET. Природа потоков пула потоков должна использоваться повторно (чтобы избежать затрат на создание / уничтожение большого количества потоков). После завершения операции поток освобождается обратно в пул для повторного использования другим запросом (сборщик мусора фактически не участвует, если вы не создали некоторые объекты в своих операциях, и в этом случае они собираются как обычно обзорный).

Что касается ASP.NET, то здесь нет специальной операции. Запрос ASP.NET завершается без учета асинхронной задачи. Единственное беспокойство может возникнуть, если ваш пул потоков насыщен (т.е. нет потоков, доступных для обслуживания запроса прямо сейчас, и настройки пула не позволяют создавать больше потоков), и в этом случае запрос блокируется в ожидании запуска задача, пока поток пула не станет доступным.

Пол Тернер
источник
Спасибо! Прочитав ваш ответ, я отредактировал вопрос с помощью примера кода. Можешь посмотреть?
Tugberk
У меня есть волшебное предложение: Task.Factory.StartNewвызов поставит в очередь операцию в пуле потоков .NET. , В этом контексте, что является правильным здесь: 1-) Он создает новый поток, и когда это будет сделано, этот поток возвращается в пул потоков и ожидает там повторного использования. 2-) Он получает поток из пула потоков, и этот поток возвращается в пул потоков и ожидает там повторного использования. 3-) Он принимает наиболее эффективный подход и может сделать любой из них.
tugberk
1
Пул потоков создает потоки по мере необходимости и перезагружает потоки, когда они не используются. Это точное поведение варьируется в зависимости от версии CLR. Вы можете найти конкретную информацию об этом здесь msdn.microsoft.com/en-us/library/0ka9477y.aspx
Пол Тернер
Это начинает складываться в моей голове сейчас. Итак, CLR владеет пулом потоков, верно? Например, приложение WPF также имеет понятие пула потоков, и оно также имеет дело с этим пулом.
Tugberk
1
Пул потоков - это отдельная вещь в CLR. Другие компоненты, которые «осведомлены» о пуле, указывают, что они используют потоки пула потоков (где это уместно), а не создают и уничтожают свои собственные. Создание или уничтожение потока - это относительно дорогая операция, поэтому использование пула значительно повышает эффективность краткосрочных операций.
Пол Тернер
2

Да, они используют поток из пула потоков. На самом деле есть очень хорошее руководство от MSDN, которое ответит на все ваши вопросы и многое другое. Я нашел это весьма полезным в прошлом. Проверьте это!

http://msdn.microsoft.com/en-us/library/ee728598.aspx

Между тем, комментарии и предложения, которые вы слышите об асинхронном коде, следует воспринимать с недоверием. Для начала, просто сделать что-то асинхронным не обязательно сделает его лучше масштабируемым, а в некоторых случаях может ухудшить масштаб вашего приложения. Другой комментарий, который вы разместили о «огромном объеме трафика ...», также верен только в определенных контекстах. Это действительно зависит от того, что делают ваши операции и как они взаимодействуют с другими частями системы.

Короче говоря, многие люди имеют много мнений об асинхронности, но они могут быть неверными вне контекста. Я бы сказал, сосредоточьтесь на ваших точных проблемах и проведите базовое тестирование производительности, чтобы увидеть, какие асинхронные контроллеры и т. Д. Действительно обрабатывают ваше приложение.

Арканзас
источник
Я читал этот документ, может быть, сотни раз, и все же у меня так много путаницы (может быть, проблема во мне, кто знает). Когда вы оглядываетесь вокруг, вы видите столько противоречивых комментариев об асинхронности в ASP.NET MVC, сколько вы можете видеть по моему вопросу.
Tugberk
для последнего предложения: внутри действия контроллера я запрашивал базу данных 5 раз отдельно (мне пришлось), и все это занимало приблизительно 400 мс. Затем я реализовал AsyncController и запустил их параллельно. Время отклика резко сократилось до прибл. 200 мс Но я понятия не имею, сколько потоков он создает, что происходит с этими потоками после того, как я с ними покончу, GCприходит и очищает их сразу после того, как я закончу, чтобы у моего приложения не было утечки памяти, и так далее, и так далее. Любая идея на эту часть.
Tugberk
прикрепите отладчик и узнайте.
AR
0

Во-первых, это не MVC, а IIS, который поддерживает пул потоков. Таким образом, любой запрос к приложению MVC или ASP.NET обслуживается из потоков, которые поддерживаются в пуле потоков. Только выполнив приложение Asynch, он вызывает это действие в другом потоке и немедленно освобождает поток, чтобы можно было выполнить другие запросы.

Я объяснил то же самое с подробным видео ( http://www.youtube.com/watch?v=wvg13n5V0V0/ «Асинхронные контроллеры MVC и голодание потоков»), в котором показано, как происходит голодание потоков в MVC и как его свести к минимуму с помощью MVC Асинхронные контроллеры. Я также измерил очереди запросов с помощью perfmon, чтобы вы могли видеть, как уменьшаются очереди запросов для асинхронного MVC и насколько он хуже для операций синхронизации.

Шивпрасад Койрала
источник