- PVSM.RU - https://www.pvsm.ru -
Прочитав статью [1] об изучении MVC и увидев комментарий [2] пользователя RouR [3], я очень заинтересовался данной темой, ну и на ходу решил перевести указанную им оригинальную статью [4].
Если и тебе, дорогой читатель, это интересно — прошу под кат!
«Я попробую показать вам, что нового несет в себе C# 5.0 с точки зрения асинхронного программирования при использовании ключевого слова await. Особенно для веб-приложений ASP.NET MVC 4.»
Я некоторое время поигрался с Visual Studio Async CTP [5] и теперь у меня достаточно соображений о том, как он работает, и как его грамотно использовать. И это значит, что я могу написать об этом.
Главная проблема большинства нашего ПО — операции, требующие длительного времени исполнения. Пользователь запускает такую операцию — и она блокирует основной поток программы до тех пор, пока не закончится ее выполнение. И в результате мы получаем неудобное пользователю ПО и недовольных клиентов. Я уверен, что каждый из нас сталкивался с такими программами!
Асинхронное программирование — далеко не новая парадигма, она уже была во времена .NET 1.0. Я не очень интересовался программированием 3 года назад, так что это понятие для меня достаточно ново и до сих пор. Насколько я читал, концепция развивалась во многом с точки зрения .NET на асинхронное программирование и, как мне кажется, находится сейчас на лучшей своей стадии.
С одной стороны, в асинхронном программировании и так очень просто запутаться, как это уже случалось со мной несколько раз. Я ходил туда-сюда и много думал о том, как же эта штука работает и где ее применить. И мне хочется верить, что в конце концов я все верно понял. С другой стороны, еще проще запутаться в асинхронном программировании, если применять его в веб-приложениях, ведь у нас нет потока UI, в который мы немедленно могли бы вывести результаты работы. Асинхронно или нет, но пользователь должен ждать некоторое время. Именно поэтому асинхронное программирование нежелательно для веб-приложений. И если вы тоже так думаете (а я прежде и сам так считал), значит вы еще ничего не поняли!
Если ваша операция выполняется синхронно и занимает много времени, то у вас нет другого выбора, как блокировать основной поток до ее завершения. А если вы делаете ту же операцию асинхронно, то это означает, что вы запускаете операцию и продолжаете что-то делать, возвращаясь к результатам вашей операции только тогда, когда она уже завершится. Между запуском и окончанием операции ваш поток свободен и может делать другие вещи. Я думаю, что большая часть людей путается именно здесь. Создание дополнительных потоков — дело накладное и может привести к проблемам, но мне кажется, что это уже в прошлом. В .NET 4.0 резко увеличили лимит на количество запускаемых потоков, но это вовсе не значит, что везде и всюду нужно создавать множество потоков — по крайней мере теперь не стоит переживать при создании очередного потока. Сейчас идут (или мне лучше сказать — уже прошли?) много споров об асинхронном программировании:
Я не знаю точного ответа на все эти вопросы, но вот вам пара примеров:
Вот, как говорится, пора бы нам в том или ином виде воспользоваться преимуществами асинхронности. Так в чем же проблема? А проблема в том, как работает данная асинхронная модель. Как говорит Андерс Хейлсберг [11], эта программная модель выворачивает наш код наизнанку. Не говоря уже о том, как очень трудно сделать вложенные вызовы асинхронных операций с обработкой исключений.
В C# 5.0 у нас будет новая модель асинхронного программирования, очень похожая на синхронную. А пока разработчики выпустили CTP для текущей версии, который доступен по Go-Live лицензии. Вот цитата из спецификации AsyncCTP:
"Асинхронные функции — новая возможность в C#, которая позволяет легко описывать асинхронные вызовы. Когда выполнение функции дойдет до выражения с ключевым словом await и начнется его выполнение, остальная часть функции будет перестроена, как автоматическое продолжение выполняемой операции и произойдет моментальный возврат из асинхронной функции, в то время как сама функция будет продолжать выполняться. Другими словами — теперь работу по выстраиванию порядка выполнения кода на себя берет сам язык, а не программист. В результате чего асинхронный код приобретает логическую структуру."
Асинхронная функция — это метод класса или анонимная функция, помеченные модификатором async. Она может возвращать как объект класса Task, так и Task<T> для произвольного типа T — в таких функциях может быть await. Если же функция, помеченная async, возвращает void (что также допустимо) — тогда использовать await не удастся. Но с другой стороны, мы же можем в качестве T использовать любой тип.
Грамотное использование асинхронности в приложениях ASP.NET MVC может очень позитивно сказаться на производительности. Верите вы или нет, но это так. Сейчас я покажу как.
Так почему же мы не везде и все делаем асинхронно? А потому что это очень тяжело, чревато ошибками и в конечном счете трудно потом поддерживать наше приложение. Но с новой асинхронной моделью это скоро изменится.
В ASP.NET MVC 4 асинхронное программирование сильно изменилось. Возможно вы знаете, что в ASP.NET MVC 3 наш контроллер должен быть унаследован от AsyncController [12] и должен соответствовать определенному шаблону. Об этом можно прочитать в статье "Использование асинхронных контроллеров в ASP.NET MVC [13]".
В ASP.NET MVC 4 нам не нужно больше использовать AsyncController, достаточно наш контроллер пометить ключевым словом async и вернуть объект класса Task или Task<T> (где T — обычно ActionResult [14]).
Я попробовал в одном приложении совместить сразу два примера, которые делают одно и то же действие — синхронно и асинхронно, соответственно. А также я провел нагрузочное тестирование, и результаты шокировали меня! Теперь давайте взглянем на код.
Во-первых, я создал простейший REST-сервис, использую новый ASP.NET Web API [10]. Простейшая модель, которая хранит коллекцию в памяти (можно было использовать БД вместо этого):
public class Car
{
public string Make;
public string Model;
public int Year;
public int Doors;
public string Colour;
public float Price;
public int Mileage;
}
public class CarService
{
public List<Car> GetCars()
{
List<Car> Cars = new List<Car>
{
new Car
{
Make="Audi",
Model="A4",
Year=1995,
Doors=4,
Colour="Red",
Price=2995f,
Mileage=122458
},
new Car
{
Make="Ford",
Model="Focus",
Year=2002,
Doors=5,
Colour="Black",
Price=3250f,
Mileage=68500
},
new Car
{
Make="BMW",
Model="5 Series",
Year=2006,
Doors=4,
Colour="Grey",
Price=24950f,
Mileage=19500
}
//This keeps going like that
};
return Cars;
}
}
И код сервиса:
public class CarsController : ApiController
{
public IEnumerable<Car> Get()
{
var service = new CarService();
return service.GetCars();
}
}
Итак, у меня есть сервис. Теперь я напишу веб-приложение, которое будет получать данные от сервиса и показывать их. Для этого я написал класс сервиса, который получает данные и десериализует их: для асинхронных вызовов я использовал новый HttpClient [15], а для синхронных — привычный WebClient [16]. Также я использовал Json.NET [17]:
public class CarRESTService
{
readonly string uri = "http://localhost:2236/api/cars";
public List<Car> GetCars()
{
using (WebClient webClient = new WebClient())
{
return JsonConvert.DeserializeObject<List<Car>>
(
webClient.DownloadString(uri)
);
}
}
public async Task<List<Car>> GetCarsAsync()
{
using (HttpClient httpClient = new HttpClient())
{
return JsonConvert.DeserializeObject<List<Car>>
(
await httpClient.GetStringAsync(uri)
);
}
}
}
Про метод GetCars сказать нечего — он прост. А вот про GetCarsAsync можно сказать следующее:
Ну и, наконец, вот он контроллер:
public class HomeController : Controller
{
private CarRESTService service = new CarRESTService();
public async Task<ActionResult> Index()
{
return View("index", await service.GetCarsAsync());
}
public ActionResult IndexSync()
{
return View("index", service.GetCars());
}
}
Итак, у нас получился Index — асинхронная функция, возвращающая Task<ActionResult>, и IndexSync — обычный синхронный вызов. Если мы перейдем по адресу /home/index или /home/indexsync, то мы не увидим особой разницы в скорости открытия страниц — она примерно одинакова.
Чтобы измерить разницу в работе этих методов, я создал нагрузочные тесты в MS Visual Studio 2010 Ultimate. Я буду в течение двух минут открывать эти две страницы, начиная с нагрузки в 50 пользователей, постепенно увеличивая ее по 20 пользователей раз в 5 секунд. Максимальным пределом будет величина в 500 пользователей. Смотрим результат (кликабельно):
Видно, что для синхронного варианта время отклика составляет 11.2 секунды, а для асинхронного — 3.65 секунды. Я думаю, разница очевидна.
Вот с этого момента я стал приверженцем концепции «Все, что выполняется дольше 40мс (операции, связанные с сетью, файловой системой — то есть весь основной ввод-вывод) — все это должно быть асинхронным!»
Автор: diger_74
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/net/5853
Ссылки в тексте:
[1] статью: http://habrahabr.ru/post/142318/
[2] комментарий: http://habrahabr.ru/post/142318/#comment_4764117
[3] RouR: http://habrahabr.ru/users/rour/
[4] оригинальную статью: http://www.tugberkugurlu.com/archive/my-take-on-task-base-asynchronous-programming-in-c-sharp-5-0-and-asp-net-mvc-web-applications
[5] Visual Studio Async CTP: http://msdn.microsoft.com/en-us/vstudio/gg316360
[6] Должны ли быть асинхронными обращения к моей БД?: http://blogs.msdn.com/b/rickandy/archive/2009/11/14/should-my-database-calls-be-asynchronous.aspx
[7] Должны ли быть асинхронными обращения к моей БД (часть 2)?: http://blogs.msdn.com/b/rickandy/archive/2011/07/19/should-my-database-calls-be-asynchronous-part-ii.aspx
[8] Как асинхронные операции в ASP.NET MVC используют потоки из пула .NET 4 (ThreadPool)?: http://stackoverflow.com/questions/8743067/do-asynchronous-operations-in-asp-net-mvc-use-a-thread-from-threadpool-on-net-4
[9] В чем недостатки использования ExecuteReaderAsync в C# AsyncCTP?: http://stackoverflow.com/questions/9432647/any-disadvantage-of-using-executereaderasync-from-c-sharp-asyncctp
[10] ASP.NET Web API: http://www.asp.net/web-api
[11] Андерс Хейлсберг: http://ru.wikipedia.org/wiki/%D0%A5%D0%B5%D0%B9%D0%BB%D1%81%D0%B1%D0%B5%D1%80%D0%B3,_%D0%90%D0%BD%D0%B4%D0%B5%D1%80%D1%81
[12] AsyncController: http://msdn.microsoft.com/en-us/library/system.web.mvc.asynccontroller.aspx
[13] Использование асинхронных контроллеров в ASP.NET MVC: http://msdn.microsoft.com/en-us/library/ee728598.aspx
[14] ActionResult: http://msdn.microsoft.com/en-us/library/system.web.mvc.actionresult(v=vs.98).aspx
[15] HttpClient: http://blogs.msdn.com/b/henrikn/archive/2012/02/16/httpclient-is-here.aspx
[16] WebClient: http://msdn.microsoft.com/en-us/library/system.net.webclient.aspx
[17] Json.NET: http://nuget.org/packages/newtonsoft.json
[18] Image: http://www.tugberkugurlu.com/Content/Images/UploadedByAuthors/wlw/77d0b156dd2b_D494/asyncFTW.png
Нажмите здесь для печати.