Поиск на сайте: Расширенный поиск


Новые программы oszone.net Читать ленту новостей RSS
CheckBootSpeed - это диагностический пакет на основе скриптов PowerShell, создающий отчет о скорости загрузки Windows 7 ...
Вы когда-нибудь хотели создать установочный диск Windows, который бы автоматически установил систему, не задавая вопросо...
Если после установки Windows XP у вас перестала загружаться Windows Vista или Windows 7, вам необходимо восстановить заг...
Программа подготовки документов и ведения учетных и отчетных данных по командировкам. Используются формы, утвержденные п...
Red Button – это мощная утилита для оптимизации и очистки всех актуальных клиентских версий операционной системы Windows...

Введение в async/await в ASP.NET

Текущий рейтинг: 4.5 (проголосовало 4)
 Посетителей: 1752 | Просмотров: 2443 (сегодня 0)  Шрифт: - +

В большинстве онлайновых материалов по async/await предполагается, что вы разрабатываете клиентские приложения, но найдется ли async место на сервере? Ответ более чем определенный: да. В этой статье даны концептуальный обзор асинхронных запросов в ASP.NET и ссылки на лучшие онлайновые ресурсы. Я не буду описывать синтаксис async или await; я уже сделал это во вводной статье в своем блоге (bit.ly/19IkogW) и в статье по эффективному применению async (msdn.microsoft.com/magazine/jj991977). Здесь я сосредоточусь на том, как async работает в ASP.NET.

В случае клиентских приложений, таких как Windows Store, настольные Windows-программы и приложения Windows Phone, основное преимущество async — отзывчивость UI. Эти типы приложений используют async главным образом для того, чтобы UI всегда отвечал на действия пользователя. В случае серверных приложений основное преимущество async — масштабируемость. Ключ к масштабируемости Node.js лежит в свойственной ей асинхронной природе, Open Web Interface for .NET (OWIN) с самого начала был разработан как асинхронный и ASP.NET тоже может быть асинхронной. Async: не только для UI-приложений!

Синхронная и асинхронная обработка запросов

Прежде чем погружаться в асинхронные обработчики запросов, я хотел бы вкратце пояснить, как действуют синхронные обработчики запросов в ASP.NET. Допустим, что запросы в системе зависят от некоего внешнего ресурса вроде базы данных или Web API. Когда поступает запрос, ASP.NET берет один из потоков в своем пуле и закрепляет запрос за ним. Поскольку обработчик запросов написан как синхронный, он будет вызывать внешний ресурс синхронно. Это блокирует поток запроса, пока вызванный внешний ресурс не вернет управление. На рис. 1 показан пул с двумя потоками, один из которых блокируется в ожидании ответа от внешнего ресурса.

*
Рис. 1. Синхронное ожидание внешнего ресурса

Thread PoolПул потоков
RequestЗапрос

В конечном счете вызов внешнего ресурса возвращает управление, и поток запроса возобновляет обработку этого запроса. После того как запрос обработан и ответ готов к отправке, поток запроса возвращается в пул.

Все это замечательно, пока ваш сервер ASP.NET не получит больше запросов, чем у него есть потоков для их обработки. В этот момент дополнительные запросы будут ждать освобождения какого-либо потока. Рис. 2 демонстрирует тот же сервер с двумя потоками при приеме трех запросов.

*
Рис. 2. Сервер с двумя потоками, получивший три запроса

В этой ситуации первые два запроса назначаются потокам из пула. Каждый из этих запросов вызывает внешний ресурс, что приводит к блокированию их потоков. Третий запрос вынужден ждать появления свободного потока, прежде чем начнется его обработка, но запрос уже находится в системе. Его таймер тикает, и есть опасность возникновения HTTP Error 503 (Service unavailable).

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

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

*
Рис. 3. Асинхронное ожидание внешнего ресурса

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

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

Почему бы не увеличить пул потоков?

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

Асинхронный код больше масштабируется, чем блокируемые потоки, поскольку он использует гораздо меньше памяти; каждый поток из пула в современной ОС имеет стек размером 1 Мб плюс невыгружаемый стек ядра. На первый взгляд кажется, что это немного. И это так, пока на сервер не начнет выполняться большое количество потоков. В противоположность этому издержки по памяти для асинхронной операции гораздо меньше. Поэтому запрос с асинхронной операцией значительно слабее «давит» на память, чем запрос с блокирующимся потоком. Асинхронный код позволяет использовать больше памяти для других операций (например, для кеширования).

Асинхронный код масштабируется быстрее, чем блокируемые потоки, потому что пул потоков имеет ограниченную скорость ввода (injection rate). На момент написания этой статьи этот показатель составлял один поток через каждые две секунды. Это ограничение скорости приема — хорошая идея: благодаря этому предотвращается постоянное конструирование и уничтожение потоков. Но давайте поразмыслим, что будет, когда на сервер внезапно обрушится водопад запросов. Синхронный код легко захлебнется, как только запросы исчерпают все доступные потоки, и оставшимся запросам придется ждать, когда пул введет новые потоки. С другой стороны, асинхронному коду такое ограничение не нужно; он «постоянен», если так можно выразиться. Асинхронный код лучше адаптируется под неожиданные всплески в объеме запросов.

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

Как насчет потока, выполняющего асинхронную работу?

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

Чтобы понять, почему масштабируются асинхронные запросы, я прослежу путь асинхронного вызова ввода-вывода на упрощенном примере. Допустим, запрос требует записи в файл. Поток запроса вызывает асинхронный метод записи. WriteAsync реализован в Base Class Library (BCL) и использует порты завершения (completion ports) для своего асинхронного ввода-вывода. Итак, вызов WriteAsync передается ОС как асинхронная операция записи в файл. После этого ОС взаимодействует со стеком драйверов, передавая по нему данные для записи в виде пакета запроса на ввод-вывод (I/O request packet, IRP).

И здесь все становится интереснее: если драйвер устройства не может сразу же обработать IRP, он должен сделать это асинхронно. Поэтому драйвер сообщает дисковому устройству начать запись и вернуть ОС ответ «операция не завершена» («pending» response). ОС передает этот ответ в BCL, а BCL возвращает незавершенную задачу коду, обрабатывающему запрос. Этот код асинхронно ожидает задачу и возвращает незавершенную задачу из этого метода и т. д. Наконец, код, обрабатывающий запрос, возвращает незавершенную задачу в ASP.NET, после чего поток запроса освобождается и возвращается в пул.

Теперь посмотрим текущее состояние системы. В операционной системе создаются различные структуры ввода-вывода (например, экземпляры Task и IRP), и все они находятся в состоянии «отложено/не завершено». Однако никакой поток не блокируется в ожидании завершения операции записи. Ни ASP.NET, ни BCL, ни ОС, ни драйвер устройства не имеют потока, выделяемого выполнению асинхронной работе.

Когда дисковое устройство заканчивает запись данных, оно уведомляет об этом свой драйвер через прерывание. Драйвер информирует ОС, что IRP завершен, а ОС сообщает об этом BCL через порт завершения. Поток из пула реагирует на это уведомление завершением задачи, возвращенной WriteAsync; в свою очередь это приводит к возобновлению выполнения кода асинхронного запроса. В течение этой фазы завершения-уведомления на очень короткие периоды времени «заимствовалось» несколько потоков, но ни один поток на самом деле не блокировался, пока шла запись.

Async и await в ASP.NET полностью относятся к вводу-выводу.

Этот пример сильно упрощен, но передает самое главное: для истинно асинхронной работы поток не требуется. Равно как не требуется и участия процессора в передаче байтов на дисковое устройство. Из этого же примера можно извлечь и второй важный урок. Подумайте о мире драйверов устройств. Такой драйвер должен обрабатывать IRP либо немедленно, либо асинхронно. Синхронной обработки нет вообще. На уровне драйверов устройств весь нетривиальный ввод-вывод является асинхронным. У многих разработчиков засело в голове представление, будто «естественный API» для операций ввода-вывода обрабатывается как синхронный и асинхронный API является уровнем над естественным, синхронным API. Однако дело обстоит с точностью до наоборот: фактически естественный API является асинхронным, и именно синхронный API реализован на основе асинхронного ввода-вывода!

Почему нет готовых асинхронных обработчиков?

Если асинхронная обработка запросов столь замечательна, почему нет готовых асинхронных обработчиков? На самом деле асинхронный код настолько хорош для масштабируемости, что платформа ASP.NET поддерживает асинхронные обработчики и модули с самого начала — с момента появления Microsoft .NET Framework. Асинхронные веб-страницы были введены в ASP.NET 2.0, а MVC получила асинхронные контроллеры в ASP.NET MVC 2.

Однако до недавнего времени асинхронный код был громоздким в написании, и его было трудно сопровождать. Многие компании решили, что легче разрабатывать код синхронным и платить за более крупные серверные фермы или за более дорогой хостинг. Теперь все кардинально поменялось: в ASP.NET 4.5 асинхронный код, использующий async и await, почти так же прост в написании, как и синхронный. По мере переноса больших систем в облака и роста потребности в масштабировании все больше и больше компаний использует async и await в ASP.NET.

Асинхронный код — не панацея

Какой бы прекрасной ни была асинхронная обработка запросов, она не решит всех ваших проблем. Существует несколько распространенных заблуждений о том, что async и await могут делать в ASP.NET.

Когда некоторые разработчики начинают осваивать async и await, они полагают, что это способ, позволяющий серверному коду «возвращать управление» клиентскому (например, браузеру). Однако async и await в ASP.NET «возвращают управление» только исполняющей среде ASP.NET; протокол HTTP остается неизменным, и вы по-прежнему получаете один ответ на один запрос. Если до введения async и await вам требовалось использовать SignalR, AJAX или UpdatePanel, то и после их применения вам понадобится использовать то же самое.

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

Async и await в ASP.NET полностью относятся к вводу-выводу. Они действительно блистают в чтении и записи файлов, баз данных и REST API. Но не столь хороши для задач, интенсивно использующих процессор. Вы можете запустить какую-то фоновую обработку, ожидая Task.Run, но делать это нет никакого смысла. По сути, это навредит масштабируемости из-за вмешательства в эвристические алгоритмы пула потоков ASP.NET. Если вам нужно выполнять в ASP.NET работу, требующую интенсивного использования процессора, то лучше всего просто запускать ее напрямую в потоке запроса. Как правило, не следует ставить работу в очередь пула потоков ASP.NET.

Наконец, подумайте о масштабируемости своей системы в целом. Лет десять назад распространенная архитектура базировалась на одном веб-сервере ASP.NET, который взаимодействовал с одной базой данных SQL Server. В такой довольно простой архитектуре узким местом для масштабируемости являлся сервер базы данных, а не веб-сервер. Переработка вызовов базы данных в асинхронные могла ничего не дать; вы могли бы использовать их для масштабирования веб-сервера, но сервер базы данных помешал бы масштабированию системы в целом.

Рик Андерсон (Rick Anderson) привел веские аргументы против асинхронных вызовов базы данных в великолепной статье «Should My Database Calls Be Asynchronous?» в своем блоге (bit.ly/1rw66UB). В ней есть два аргумента в поддержку того, о чем я писал чуть выше. Во-первых, асинхронный код сложен (а значит, требует столь больших расходов на разработку, что проще купить более мощные серверы), а во-вторых, масштабировать веб-сервер не имеет особого смысла, если узким местом является сервер базы данных. Оба эти аргумента действительно были вескими в то время, когда была написана эта статья, но со временем они перестали быть таковыми. Во-первых, асинхронный код стал гораздо проще с появлением языковых средств async и await. Во-вторых, серверные части с данными для веб-сайтов стали хорошо масштабируемыми, когда мир стал переходить на облачные вычисления. Современные серверные базы данных вроде Microsoft Azure SQL Database, NoSQL и другие API способны к радикально большему масштабированию, чем единственный SQL Server, из-за чего узким местом теперь становится веб-сервер. В этом сценарии async/await могут дать вам колоссальный выигрыш за счет масштабирования ASP.NET.

Прежде чем начинать работу

Прежде всего вы должны понимать, что async и await поддерживаются только ASP.NET 4.5. Имеется NuGet-пакет Microsoft.Bcl.Async, который вводит поддержку async и await для .NET Framework 4, но не используйте его — он не будет работать корректно! Причина в том, что в самой ASP.NET нужно было изменить принципы управления ее асинхронной обработкой запросов для более эффективной работы с async и await; NuGet-пакет содержит все типы, необходимые компилятору, но он не вносит исправления в исполняющую среду ASP.NET. Никакого обходного способа нет; вам нужна ASP.NET 4.5 или выше.

Далее вам следует знать, что ASP.NET 4.5 вводит на сервере режим совместимости (quirks mode). Если вы создаете новый проект ASP.NET 4.5, вам не о чем беспокоиться. Но, если вы обновляете существующий проект до ASP.NET 4.5, включаются все «изыски» режима совместимости. Советую отключить их все, отредактировав ваши web.config и установив httpRuntime.targetFramework в 4.5. Если ваше приложение падает при такой настройке (и вы не хотите тратить время на устранение причины), то по крайней мере вы можете заставить async/await работать, добавив ключ appSetting в aspnet:UseTaskFriendlySynchronizationContext со значением «true». Ключ appSetting не обязателен, если httpRuntime.targetFramework установлен в 4.5. Группа веб-разработок описала все детали работы нового режима совместимости в своей публикации по ссылке bit.ly/1pbmnzK.

Совет    Если вы наблюдаете странное поведение или исключения и ваш стек вызовов включает LegacyAspNetSynchronizationContext, значит, ваше приложение выполняется в этом режиме совместимости. LegacyAspNetSynchronizationContext несовместим с async; в ASP.NET 4.5 вам нужен обычный AspNetSynchronizationContext.

В ASP.NET 4.5 все параметры ASP.NET имеют хорошие значения по умолчанию для асинхронных запросов, но, возможно, вы захотите изменить пару других параметров. Первый из них связан с настройкой IIS: подумайте о том, чтобы увеличить лимит очереди IIS/HTTP.sys (Application Pools | Advanced Settings | Queue Length) с 1000 (по умолчанию) до 5000. Другой относится к настройке исполняющей среды .NET: ServicePointManager.DefaultConnectionLimit, значение которого по умолчанию в 12 раз превышает количество ядер. DefaultConnectionLimit ограничивает количество одновременных входящих соединений.

Пара слов об отмене запросов

Когда ASP.NET обрабатывает запрос синхронно, механизм отмены запроса очень прост (например, если истекло время ожидания обработки запроса): она отменит рабочий поток для этого запроса. Это имеет смысл в синхронном мире, где каждому запросу выделяется свой рабочий поток на все время от начала до конца. Отмена потоков вредит долгосрочной стабильности AppDomain, поэтому по умолчанию ASP.NET будет регулярно перезапускать ваше приложение для очистки.

В случае асинхронных запросов ASP.NET не будет отменять рабочие потоки, если ей нужно отменить какой-то запрос. Вместо этого она отменит запрос, используя CancellationToken. Асинхронные обработчики запросов должны принимать маркеры отмены и подчиняться им. В большинстве новых инфраструктур (включая Web API, MVC и SignalR) CancellationToken будет конструировать и передаваться вам напрямую; от вас требуется лишь объявить его как параметр. Вы также можете напрямую обращаться к маркерам ASP.NET; например, HttpRequest.TimedOutToken — это CancellationToken, который отменяет запрос, когда истекает время, отведенное на его обработку.

По мере переноса приложений в облако отмена запросов становится более важной. Облачные приложения в большей мере зависимы от внешних сервисов, вызовы которых могут занимать произвольные периоды времени. Например, один из стандартных шаблонов — повторно передавать внешние запросы с экспоненциальным алгоритмом отсрочки (exponential backoff); если ваше приложение зависит от множества подобных сервисов, лучше всего указывать максимальные интервалы ожидания при обработке запросов.

Текущее состояние поддержки async

Многие библиотеки обновлены для совместимости с async. Поддержка async добавлена в Entity Framework (в NuGet-пакете EntityFramework) в версии 6. Но будьте осторожны и избегайте отложенной загрузки (lazy loading) при асинхронной работе, так как отложенная загрузка всегда выполняется синхронно. HttpClient (в NuGet-пакете Microsoft.Net.Http) является современным HTTP-клиентом, изначально рассчитанным на использование async и идеальным для вызова внешних REST API; это современная замена для HttpWebRequest и WebClient. Microsoft Azure Storage Client Library (в NuGet-пакете WindowsAzure.Storage) получила поддержку async в версии 2.1.

Более новые инфраструктуры, такие как Web API и SignalR, полностью поддерживают async и await. В частности, весь конвейер Web API опирается на поддержку async: асинхронными являются не только контроллеры, но и фильтры, а также обработчики. Web API и SignalR имеют очень естественную историю поддержки async: вы можете «просто делать это», и «это просто работает».

И здесь нам придется перейти к более грустной истории: на сегодняшний день ASP.NET MVC лишь частично поддерживает async и await. Базовая поддержка есть: асинхронные операции контроллера и отмена асинхронных операций работают должным образом. Великолепное учебное пособие по использованию асинхронных операций контроллера в ASP.NET MVC см. по ссылке bit.ly/1m1LXTx; это лучший ресурс по применению async в MVC. К сожалению, ASP.NET MVC в настоящее время не поддерживает асинхронные фильтры (bit.ly/1oAyHLc) или дочерние асинхронные операции (bit.ly/1px47RG).

ASP.NET Web Forms — более старая инфраструктура, но в ней есть адекватная поддержка async и await. И вновь лучший ресурс, с которого вам стоит начать, — учебное пособие по async в Web Forms по ссылке bit.ly/Ydho7W. В случае Web Forms поддержку async нужно включать вручную. Сначала вы должны установить Page.Async в true, затем с помощью PageAsyncTask зарегистрировать асинхронную работу с этой страницей (в качестве альтернативы можно использовать асинхронные void-обработчики событий). PageAsyncTask также поддерживает отмену.

Если у вас есть собственный HTTP-обработчик или модуль, то ASP.NET теперь поддерживает и их асинхронные версии. HTTP-обработчики поддерживаются через HttpTaskAsyncHandler (bit.ly/1nWpWFj), а HTTP-модули — через EventHandlerTaskAsyncHelper (bit.ly/1m1Sn4O).

На момент написания этой статьи группа ASP.NET работала над новым проектом, известным как ASP.NET vNext. Во vNext весь конвейер является асинхронным по умолчанию. В настоящее время планируется объединить MVC и Web API в единую инфраструктуру с полной поддержкой async и await (включая асинхронные фильтры и асинхронные компоненты представлений). Другие инфраструктуры, готовые к использованию async, такие как SignalR, естественным образом «найдут свой дом» во vNext. Воистину будущее асинхронно!

Страховочные сетки

В ASP.NET 4.5 введено несколько новых «страховочных сеток» («safety nets»), помогающих вам отлавливать проблемы с асинхронной работой в вашем приложении. Они включены по умолчанию и должны оставаться включенными.

Если синхронный обработчик попытается выполнить асинхронную работу, вы получите InvalidOperationException с сообщением «An asynchronous operation cannot be started at this time» («асинхронную операцию нельзя запустить в данное время»). Основных причин этого исключения две. Первая — страница Web Forms имеет асинхронные обработчики событий, но Page.Async не установлен в true. И вторая — синхронный код вызывает асинхронный void-метод. Вот еще одна причина, почему следует избегать асинхронных void-методов.

Другая страховочная сетка предназначена для асинхронных обработчиков: когда асинхронный обработчик прекращает обработку запроса, но ASP.NET обнаруживает, что асинхронная работа не закончена, вы получаете InvalidOperationException с сообщением «An asynchronous module or handler completed while an asynchronous operation was still pending» («асинхронный модуль или обработчик завершился, тогда как асинхронная операция еще не выполнена»). Это обычно происходит из-за того, что асинхронный код вызывает асинхронный void-метод, но возможно и из-за неправильного использования компонента EAP (Event-based Asynchronous Pattern) (bit.ly/19VdUWu).

Обе страховочные сетки можно отключить с помощью параметра HttpContext.AllowAsyncDuringSyncStages (его можно задать и в web.config). На некоторых страницах в Интернете предлагается такой вариант всякий раз, когда вы получаете эти исключения. Категорически не могу согласиться с этими предложениями. Более того, я не знаю, почему такая возможность вообще предусмотрена. Отключение страховочных сеток — ужасная идея. Единственная возможная причина, которая приходит мне в голову, — ваш код уже выполняет некую чрезвычайно продвинутую асинхронную работу (за границами того, что я когда-либо пытался делать), и вы являетесь гением в области многопоточности. Так что, если вы читали эту статью, зевая и думая: «Ради бога, я же не нуб», тогда вы вполне можете подумать об отключении этих страховочных сеток. Для остальных эта возможность крайне опасна, и никогда не отключайте страховочные сетки, если вы не полностью понимаете последствия этого шага.

Приступаем к работе

Наконец-то! Вы готовы задействовать преимущества async и await? Ценю ваше терпение.

Сначала просмотрите раздел «Асинхронный код — не панацея» в этой статье, чтобы убедиться, что в вашей архитектуре async/await даст выигрыш. Потом обновите свое приложение до ASP.NET 4.5 и отключите режим совместимости (quirks mode) (неплохо было бы запустить приложение и посмотреть, не сломалось ли в нем что-нибудь). К этому моменту вы готовы приступить к работе с async/await.

Начните с «листьев» («leaves»). Подумайте о том, как обрабатываются ваши запросы, и идентифицируйте любые операции ввода-вывода, особенно сетевые. Распространенные примеры — запросы к базам данных и команды, а также вызовы других веб-сервисов и API. Выберите один из них, с которого вы начнете, и поэкспериментируйте, чтобы найти лучший вариант выполнения соответствующей операции с использованием async/await. Многие встроенные в BCL типы теперь являются готовыми к использованию async в .NET Framework 4.5, например SmtpClient имеет методы SendMailAsync. У некоторых типов есть заменители, готовые к асинхронной работе, например HttpWebRequest и WebClient можно заменить на HttpClient. При необходимости обновите библиотеку, например в Entity Framework введены методы, совместимые с async, в версии 6.

Но избегайте «фальшивой асинхронности» в библиотеках. Фальшивая асинхронность заключается в том, что компонент имеет API, готовый к асинхронной работе, но реализован простым обертыванием синхронного API в потоке из пула. Это контрпродуктивно в отношении масштабируемости в ASP.NET. Один из ярких примеров фальшивой асинхронности — Newtonsoft JSON.NET, которая в остальном является превосходной библиотекой. Лучше не вызывать (фальшивые) асинхронные версии для сериализации JSON, а просто использовать синхронные версии. Менее очевидный пример фальшивой асинхронности — файловые потоки в BCL. Файловый поток нужно явным образом открывать для асинхронного доступа; в ином случае он будет использовать фальшивую асинхронность, синхронно блокируя поток из пула на файловых операциях чтения и записи.

Выбрав лист, начните с какого-нибудь метода в своем коде, который вызывает этот API, и сделайте его асинхронным, вызывающим асинхронный API через await. Если вызываемый вами API поддерживает CancellationToken, ваш метод должен принимать CancellationToken и передавать его API-методу.

Всякий раз, когда вы помечаете метод как async, вы должны изменять его возвращаемый тип: void — в Task, а тип T, отличный от void, — в Task<T>. После этого вы обнаружите, что все вызывающие этот метод должны быть асинхронными, чтобы они могли ожидать выполнения задачи через await, и т. д. Кроме того, добавьте слово «Async» к имени своего метода для соответствия соглашениям Task-based Asynchronous Pattern (bit.ly/1uBKGKR).

Разрешите шаблону async/await делать так, чтобы ваш стек вызовов разрастался в сторону «ствола» («trunk»). В стволе дерева ваш код будет взаимодействовать с инфраструктурой ASP.NET (MVC, Web Forms, Web API). Прочитайте раздел «Текущее состояние поддержки async» ранее в этой статье для интеграции асинхронного кода с инфраструктурой.

Попутно идентифицируйте состояние, локальное для потока. Поскольку асинхронные запросы могут изменять потоки, это состояние, например ThreadStaticAttribute, ThreadLocal<T>, слоты данных потока и CallContext.GetData/SetData, работать не будет. По возможности замените его на HttpContext.Items или храните неизменяемые данные в CallContext.LogicalGetData/LogicalSetData.

Хотел бы дать вам полезный совет: вы можете (временно) дублировать свой код для создания вертикального раздела (vertical partition). Благодаря такому приему вы не меняете свои синхронные методы на асинхронные — вы копируете весь синхронный метод, а затем модифицируете его копию так, чтобы она стала асинхронной. Тем самым большая часть приложения по-прежнему использует синхронные методы, а вы просто создаете небольшой вертикальный слой асинхронности. Это очень удобно, если вы хотите исследовать применение асинхронности для проверки концепции или провести нагрузочное тестирование лишь некоей части приложения, чтобы получить представление, насколько ваша система сможет масштабироваться. У вас может быть один полностью асинхронный запрос (или страница), а остальные части приложения по-прежнему синхронные. Конечно, вам не нужно хранить дубликаты для каждого метода; в конечном счете весь код, связанный с вводом-выводом, станет асинхронным, после чего синхронные копии можно будет удалить.

Заключение

Надеюсь, эта статья помогла вам получить концептуальное представление об асинхронных запросах в ASP.NET. Используя async и await, писать веб-приложения, сервисы и API, способные максимально задействовать существующие серверные ресурсы, гораздо легче. Асинхронность — потрясающая штука!

Автор: Введение в async/await в ASP.NET  •  Иcточник: msdn.microsoft.com  •  Опубликована: 21.09.2015
Нашли ошибку в тексте? Сообщите о ней автору: выделите мышкой и нажмите CTRL + ENTER
Теги:   ASP.NET.


Оценить статью:
Вверх
Комментарии посетителей
04.02.2017/21:34  AkmalSalikhov

Обновите, пожалуста, ссылки. Они все битые.
Спасибо.
Комментарии отключены. С вопросами по статьям обращайтесь в форум.