Использование асинхронных методов в ASP.NET MVC 4

по Рик Андерсон (

В этом учебнике вы узнаете об основах создания асинхронного веб-приложения ASP.NET MVC с помощью Visual Studio Express 2012 для Web, которое является бесплатной версией Microsoft Visual Studio. Также можно использовать Visual Studio 2012.

Полный пример для этого руководства приведен на сайте GitHub https://github.com/RickAndMSFT/Async-ASP.NET/

Класс контроллера ASP.NET MVC 4 в сочетании с .NET 4,5 позволяет создавать асинхронные методы действия, возвращающие объект типа Task<ActionResult>. В .NET Framework 4 появилась концепция асинхронного программирования, называемая задачей , а ASP.NET MVC 4 поддерживает задачу. Задачи представлены типом задачи и связанными типами в пространстве имен System. Threading. Tasks . .NET Framework 4,5 построена на этой асинхронной поддержке с помощью ключевых слов await и Async , которые делают работу с объектами задач гораздо менее сложными, чем предыдущие асинхронные подходы. Ключевое слово await — это сокращенная синтаксическая форма, указывающая, что фрагмент кода должен асинхронно ожидать какой-либо другой фрагмент кода. Ключевое слово Async представляет указание, которое можно использовать для пометки методов как асинхронных методов, основанных на задачах. Сочетание await, Asyncи объекта Task значительно упрощает написание асинхронного кода в .NET 4,5. Новая модель для асинхронных методов называется асинхронной моделью на основе задач (TAP). В этом учебнике предполагается, что вы знакомы с асинхронной программой с помощью ключевых слов await и Async и пространства имен Task .

Дополнительные сведения о ключевых словах с использованием await и Async и пространстве имен задачи см. в следующих статьях.

Обработка запросов пулом потоков

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

Это может не быть проблемой, так как пул потоков можно сделать достаточно большим для размещения множества занятых потоков. Однако количество потоков в пуле потоков ограничено (максимальное значение по умолчанию для .NET 4,5 — 5 000). В больших приложениях с высоким параллелизмом долго выполняющихся запросов все доступные потоки могут быть заняты. Такая ситуация называется нехваткой потоков. При достижении этого условия веб-сервер помещает запросы в очередь. Если очередь запросов заполнена, веб-сервер отклоняет запросы с состоянием HTTP 503 (сервер слишком занят). Пул потоков CLR имеет ограничения на новые внедрения потоков. Если параллелизм — это пакетная обработка (то есть веб-сайт может получить большое количество запросов), а все доступные потоки запросов заняты из-за внутренних вызовов с высокой задержкой, ограниченная скорость внедрения потоков может привести к плохому реагированию приложения. Кроме того, каждый новый поток, добавляемый в пул потоков, имеет накладные расходы (например, 1 МБ стековой памяти). Веб-приложение, использующее синхронные методы для обработки вызовов с высокой задержкой, когда пул потоков увеличился до версии .NET 4,5 по умолчанию (5), 000 потоков потребляет примерно 5 ГБ памяти, чем приложение может обрабатывать те же запросы с помощью асинхронные методы и только 50 потоков. При выполнении асинхронной работы вы не всегда используете поток. Например, при выполнении асинхронного запроса веб-службы ASP.NET не будет использовать потоки между асинхронным вызовом метода и await. Использование пула потоков для запросов на обслуживание с высокой задержкой может привести к увеличению объема памяти и низкому использованию аппаратного обеспечения сервера.

Обработка асинхронных запросов

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

Выбор синхронных или асинхронных методов действия

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

Как правило, используйте синхронные методы для следующих условий:

  • Операции являются простыми и кратковременными.
  • Простота имеет большое значение, чем эффективность.
  • Операции в основном являются операциями ЦП, а не операциями, в которых широко используется диск или сетевые ресурсы. Использование асинхронных методов действия в операциях, которые связаны с ЦП, не дает преимущества и приводит к дополнительному расходу ресурсов.

Как правило, используйте асинхронные методы для следующих условий:

  • Вы вызываете службы, которые могут использоваться асинхронными методами, и вы используете .NET 4,5 или более поздней версии.
  • Операции связаны с использованием сети или ввода-вывода, а не с ЦП.
  • Параллелизм имеет большее значение, чем простота кода.
  • Необходимо предоставить механизм, который позволяет пользователям отменить длительные по времени запросы.
  • Если преимущество переключения потоков превышает затраты на переключение контекста. Как правило, метод следует делать асинхронным, если синхронный метод ожидает поток запроса ASP.NET, не выполняя никаких действий. Делая вызов асинхронным, поток запроса ASP.NET не зависает, пока ожидает завершения запроса веб-службы.
  • Тестирование показывает, что блокирующие операции являются узким местом в производительности сайта и что IIS может обслуживать больше запросов с помощью асинхронных методов для этих блокирующих вызовов.

В примере показано, как эффективно использовать асинхронные методы действия. Представленный образец предназначен для простой демонстрации асинхронного программирования в ASP.NET MVC 4 с использованием .NET 4,5. Образец не предназначен для использования в качестве эталонной архитектуры для асинхронного программирования в ASP.NET MVC. Пример программы вызывает веб-API ASP.NET методы, которые в свою очередь вызывают Task. Delay для имитации длительно выполняемых вызовов веб-служб. Большинство рабочих приложений не будут показывать такие очевидные преимущества при использовании асинхронных методов действий.

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

Пример приложения

Вы можете скачать пример приложения из https://github.com/RickAndMSFT/Async-ASP.NET/ на сайте GitHub . Репозиторий состоит из трех проектов:

  • Mvc4Async: проект ASP.NET MVC 4, содержащий код, используемый в этом руководстве. Он выполняет вызовы веб-API в службу вебапипгв .
  • Вебапипгв: проект веб-API ASP.NET MVC 4, который реализует контроллеры Products, Gizmos and Widgets. Он предоставляет данные для проекта вебаппасинк и проекта Mvc4Async .
  • Вебаппасинк: проект веб-форм ASP.NET, используемый в другом учебнике.

Синхронный метод действия приспособлений

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

public ActionResult Gizmos()
{
    ViewBag.SyncOrAsync = "Synchronous";
    var gizmoService = new GizmoService();
    return View("Gizmos", gizmoService.GetGizmos());
}

В следующем коде показан метод GetGizmos службы гизмо.

public class GizmoService
{
    public async Task<List<Gizmo>> GetGizmosAsync(
        // Implementation removed.
       
    public List<Gizmo> GetGizmos()
    {
        var uri = Util.getServiceUri("Gizmos");
        using (WebClient webClient = new WebClient())
        {
            return JsonConvert.DeserializeObject<List<Gizmo>>(
                webClient.DownloadString(uri)
            );
        }
    }
}

Метод GizmoService GetGizmos передает универсальный код ресурса (URI) веб-API ASP.NET службе HTTP, которая возвращает список данных приспособлений. Проект вебапипгв содержит реализацию gizmos, widget и контроллеров product веб-API.
На следующем рисунке показано представление приспособлений из примера проекта.

Приспособлений

Создание асинхронного метода действия приспособлений

В примере используются новые ключевые слова Async и await (доступные в .NET 4,5 и Visual Studio 2012), позволяющие компилятору отвечать за поддержание сложных преобразований, необходимых для асинхронного программирования. Компилятор позволяет писать код с помощью C#синхронных конструкций потока управления, и компилятор автоматически применяет преобразования, необходимые для использования обратных вызовов, чтобы избежать блокировки потоков.

В следующем коде показан синхронный метод Gizmos и асинхронный метод GizmosAsync. Если браузер поддерживает элемент HTML 5 <mark>, вы увидите, что изменения в GizmosAsync выделены желтым цветом.

public ActionResult Gizmos()
{
    ViewBag.SyncOrAsync = "Synchronous";
    var gizmoService = new GizmoService();
    return View("Gizmos", gizmoService.GetGizmos());
}
public async Task<ActionResult> GizmosAsync()
{
    ViewBag.SyncOrAsync = "Asynchronous";
    var gizmoService = new GizmoService();
    return View("Gizmos", await gizmoService.GetGizmosAsync());
}

Для обеспечения асинхронности GizmosAsync были применены следующие изменения.

  • Метод помечается ключевым словом Async , которое указывает компилятору создавать обратные вызовы для частей тела и автоматически создавать Task<ActionResult>, которые возвращаются.
  • "Async" был добавлен к имени метода. Добавление "Async" не является обязательным, но является соглашением при написании асинхронных методов.
  • Тип возвращаемого значения изменен с ActionResult на Task<ActionResult>. Тип возвращаемого значения Task<ActionResult> представляет текущую работу и предоставляет вызывающие методы метода с помощью обработчика, который ожидает завершения асинхронной операции. В этом случае вызывающей стороной является веб-служба. Task<ActionResult> представляет текущую работу с результатом ActionResult.
  • Ключевое слово await было применено к вызову веб-службы.
  • Вызван API асинхронной веб-службы (GetGizmosAsync).

В теле метода GetGizmosAsyncа другой асинхронный метод, вызывается GetGizmosAsync. GetGizmosAsync немедленно возвращает Task<List<Gizmo>>, который в конечном итоге завершится, когда данные станут доступны. Так как вы не хотите делать что-то еще, пока не гизмо данные, код ожидает задачу (с помощью ключевого слова await ). Ключевое слово await можно использовать только в методах, снабженных ключевым словом Async .

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

В следующем коде приведены методы GetGizmos и GetGizmosAsync.

public List<Gizmo> GetGizmos()
{
    var uri = Util.getServiceUri("Gizmos");
    using (WebClient webClient = new WebClient())
    {
        return JsonConvert.DeserializeObject<List<Gizmo>>(
            webClient.DownloadString(uri)
        );
    }
}
public async Task<List<Gizmo>> GetGizmosAsync()
{
    var uri = Util.getServiceUri("Gizmos");
    using (HttpClient httpClient = new HttpClient())
    {
        var response = await httpClient.GetAsync(uri);
        return (await response.Content.ReadAsAsync<List<Gizmo>>());
    }
}

Асинхронные изменения похожи на те, которые были сделаны в гизмосасинк выше.

  • Сигнатура метода была помечена ключевым словом Async , тип возвращаемого значения был изменен на Task<List<Gizmo>>, а Async был добавлен к имени метода.
  • Вместо класса WebClient используется асинхронный класс HttpClient .
  • Ключевое слово await было применено к асинхронным методам HttpClient .

На следующем рисунке показано асинхронное представление гизмо.

async

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

Параллельное выполнение нескольких операций

Асинхронные методы действия имеют значительное преимущество по сравнению с синхронными методами, когда действие должно выполнять несколько независимых операций. В предоставленном примере синхронный метод PWG(для продуктов, мини-приложений и приспособлений) отображает результаты трех вызовов веб-службы для получения списка продуктов, мини-приложений и приспособлений. Проект веб-API ASP.NET , предоставляющий эти службы, использует Task. Delay для имитации задержки или медленных сетевых вызовов. Если задержка задана равным 500 миллисекундам, асинхронный метод PWGasync выполняет несколько более 500 миллисекунд, пока синхронная PWGная версия занимает свыше 1 500 миллисекунд. Синхронный PWG метод показан в следующем коде.

public ActionResult PWG()
{
    ViewBag.SyncType = "Synchronous";
    var widgetService = new WidgetService();
    var prodService = new ProductService();
    var gizmoService = new GizmoService();

    var pwgVM = new ProdGizWidgetVM(
        widgetService.GetWidgets(),
        prodService.GetProducts(),
        gizmoService.GetGizmos()
       );

    return View("PWG", pwgVM);
}

Асинхронный метод PWGasync показан в следующем коде.

public async Task<ActionResult> PWGasync()
{
    ViewBag.SyncType = "Asynchronous";
    var widgetService = new WidgetService();
    var prodService = new ProductService();
    var gizmoService = new GizmoService();

    var widgetTask = widgetService.GetWidgetsAsync();
    var prodTask = prodService.GetProductsAsync();
    var gizmoTask = gizmoService.GetGizmosAsync();

    await Task.WhenAll(widgetTask, prodTask, gizmoTask);

    var pwgVM = new ProdGizWidgetVM(
       widgetTask.Result,
       prodTask.Result,
       gizmoTask.Result
       );

    return View("PWG", pwgVM);
}

На следующем рисунке показано представление, возвращаемое методом пвгасинк .

пвгасинк

Использование токена отмены

Асинхронные методы действия, возвращающие Task<ActionResult>, являются отменяемыми, то есть принимают параметр CancellationToken , если он предоставляется с атрибутом AsyncTimeout . В следующем коде показан метод GizmosCancelAsync с временем ожидания 150 миллисекунд.

[AsyncTimeout(150)]
[HandleError(ExceptionType = typeof(TimeoutException),
                                    View = "TimeoutError")]
public async Task<ActionResult> GizmosCancelAsync(
                       CancellationToken cancellationToken )
{
    ViewBag.SyncOrAsync = "Asynchronous";
    var gizmoService = new GizmoService();
    return View("Gizmos",
        await gizmoService.GetGizmosAsync(cancellationToken));
}

В следующем коде показана перегрузка Жетгизмосасинк, которая принимает параметр CancellationToken .

public async Task<List<Gizmo>> GetGizmosAsync(string uri,
    CancellationToken cancelToken = default(CancellationToken))
{
    using (HttpClient httpClient = new HttpClient())
    {
        var response = await httpClient.GetAsync(uri, cancelToken);
        return (await response.Content.ReadAsAsync<List<Gizmo>>());
    }
}

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

Конфигурация сервера для вызовов веб-службы с высоким уровнем параллелизма и высокой задержкой

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

  • Windows 7, Windows Vista и все клиентские операционные системы Windows имеют максимум 10 одновременных запросов. Для просмотра преимуществ асинхронных методов при высокой нагрузке потребуется операционная система Windows Server.

  • Зарегистрируйте .NET 4,5 со службами IIS из командной строки с повышенными привилегиями:
    %windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_regiis-i
    См . раздел ASP.NET IIS Registration Tool (Aspnet_regiis. exe) .

  • Может потребоваться увеличить ограничение очереди http. sys на значение по умолчанию 1 000 до 5 000. Если значение параметра слишком мало, то можно увидеть запросы отклонения http. sys с состоянием HTTP 503. Чтобы изменить ограничение очереди HTTP. sys, сделайте следующее:

    • Откройте диспетчер IIS и перейдите в область пулы приложений.
    • Щелкните правой кнопкой мыши целевой пул приложений и выберите Дополнительные параметры.
      дополнительные
    • В диалоговом окне Дополнительные параметры измените длину очереди с 1 000 на 5 000.
      Длина очереди

    Обратите внимание, что на приведенных выше изображениях платформа .NET Framework указана как версия 4.0, несмотря на то, что пул приложений использует .NET 4,5. Чтобы понять это расхождение, см. следующие сведения:

  • Если приложение использует веб-службы или System.NET для взаимодействия с серверной частью по протоколу HTTP, может потребоваться увеличить элемент элемент connectionManagement/maxConnection . Для приложений ASP.NET это ограничено функцией автонастройки в 12 раз больше, чем количество процессоров. Это означает, что на четырехъядерном уровне можно использовать не более 12 * 4 = 48 одновременных подключений к конечной точке IP. Поскольку это связано с автоматической конфигурацией, самый простой способ увеличить maxconnection в приложении ASP.NET — установить System .NET. ServicePointManager. DefaultConnectionLimit программно в метод from Application_Start в файле Global. asax . Пример см. в примере загрузки.

  • В .NET 4,5 значение по умолчанию 5000 для максконкуррентрекуестсперкпу должно быть точным.