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

после этого вопроса мне становится комфортно при использовании async операции в ASP.NET MVC. Итак, я написал два сообщения в блоге об этом:

у меня слишком много недоразумения в моем сознании об асинхронных операциях на ASP.NET MVC.

Я всегда слышу эту фразу: приложение может масштабироваться лучше, если операции выполняются асинхронно

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

Я думаю, что эти два предложения противоречивы.

у меня нет много информации о том, как работает threadpool ASP.NET но я знаю, что threadpool имеет ограниченный размер для потоков. Итак, второе предложение должно быть связано с этим вопросом.

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

например, когда мы реализуем AsyncController, как структуры приложений? Если я получаю огромный трафик, является ли хорошей идеей реализовать AsyncController?

есть ли кто-нибудь, кто может снять этот черный занавес перед моими глазами и объяснить мне сделку об асинхронности на ASP.NET MVC 3 (NET 4)?

Edit:

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

использование асинхронного контроллера в ASP.NET MVC

Edit:

предположим, что у меня есть действие контроллера, как показано ниже (не реализация AsyncController правда):

public ViewResult Index() { 

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

    return View();
}

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

в этом случае это должно использовать поток из threadpool? Если да, то после его завершения, что случается с этой нитью? Делает GC приходит и убирает сразу после его завершения?

Edit:

для ответа @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

    }
}

6 ответов


здесь отличная статья Я бы рекомендовал вам прочитать, чтобы лучше понять асинхронную обработку в ASP.NET (что в основном представляют собой асинхронные контроллеры).

давайте сначала рассмотрим стандартное синхронное действие:

public ActionResult Index()
{
    // some processing
    return View();
}

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

Теперь возьмем пример асинхронного шаблона:

public void IndexAsync()
{
    // perform some processing
}

public ActionResult IndexCompleted(object result)
{
    return View();
}

когда запрос отправляется в действие индекса, поток извлекается из пула потоков и тела IndexAsync метод выполняется. Как только тело этого метода завершается, поток возвращается в поток бассейн. Затем, используя стандарт AsyncManager.OutstandingOperations, как только вы сигнализируете о завершении асинхронной операции, из пула потоков и тела IndexCompleted на нем выполняется действие и выводится результат клиенту.

таким образом, мы можем видеть в этом шаблоне, что один клиентский HTTP-запрос может быть выполнен двумя разными потоками.

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

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

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

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

при получении запроса на действие индекса поток тянет из пула в выполните его тело, но его тело только планирует новую задачу, используя TPL. Таким образом, выполнение действия заканчивается, и поток возвращается в пул потоков. Кроме этого,TPL использует потоки из пула потоков для выполнения их обработки. Поэтому, даже если исходный поток был возвращен в пул потоков, вы нарисовали другой поток из этого пула для выполнения тела задачи. Итак, вы поставили под угрозу 2 нити из драгоценных бассейн.

теперь давайте рассмотрим следующее:

public ViewResult Index() { 

    new Thread(() => { 
        //Do an advanced looging here which takes a while
    }).Start();

    return View();
}

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


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

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

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

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

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

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


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

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 ().

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

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


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

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

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

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

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

Edit:

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

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


Да, они используют поток из пула потоков. На самом деле есть довольно отличное руководство от MSDN, которое будет решать все ваши вопросы и многое другое. В прошлом я находил его весьма полезным. Зацени!

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

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

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


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

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