ASP.NET --- ASP.NET MVC 5 --- Поліпшення продуктивності за допомогою контролерів
Інфраструктура MVC Framework пропонує два спеціальних виду контролерів які можуть поліпшити продуктивність розроблюваних веб-додатків MVC. Як і в разі будь-якої оптимізації продуктивності, при роботі з цими контролерами доведеться або принести в жертву простоту використання, або мати справу з обмеженою функціональністю. У наступних розділах буде продемонстровано застосування обох видів контролерів і дані пояснення пов'язаних з ними переваг і недоліків.
Використання контролерів, які не підтримують стан сеансу
За замовчуванням контролери підтримують стан сеансу, яке може використовуватися для зберігання значень даних між запитами, спрощуючи рішення задач програмістам MVC. Створення та підтримка стану сеансу - складний процес. Дані повинні зберігатися і вилучатись, а самі сеанси - управлятися для забезпечення належної політики старіння. Дані сеансу витрачають пам'ять на сервері або в якомусь іншому сховище, і потреба в синхронізації даних між безліччю веб-серверів ускладнює запуск програми на декількох серверах (для великих додатків).
Для спрощення підтримки стану сеансу ASP.NET буде обробляти тільки один запит для заданого сеансу за раз. Якщо клієнт видає безліч перекриваються в часі запитів, сервер буде ставити їх в чергу і обробляти послідовно. Перевага такого підходу в тому, що вам не потрібно переживати з приводу модифікації одних і тих же даних багатьма запитами одночасно. Недолік пов'язаний з неможливістю досягнення необхідної пропускної здатності запитів.
Не всі контролери потребують засобі стану сеансу. У таких випадках продуктивність програми можна поліпшити, уникнувши роботи, яка пов'язана з підтримкою стану сеансу. Це робиться за рахунок застосування контролерів, які не підтримують стан сеансу. Вони дуже схожі на звичайні контролери, але з двома винятками: інфраструктура MVC Framework НЕ буде завантажувати або зберігати стан сеансу, коли такі контролери використовуються для обробки запиту, і перекриваються запити можуть бути оброблені одночасно.
Управління станом сеансу в спеціальній фабриці IControllerFactory
Під час обговорення фабрики контролерів в одній з попередніх статей ми показали, що інтерфейс IControllerFactory містить метод на ім'я GetControllerSessionBehavior (). який повертає значення перерахування SessionStateBehavior. Це перерахування містить чотири значення, які керують зміною стану сеансу в контролері:
Значення перерахування SessionStateBehavior
Стан сеансу повністю відключено
Фабрика контролерів, яка реалізує інтерфейс IControllerFactory безпосередньо, встановлює поведінку стану сеансу для контролерів, повертаючи значення SessionStateBehavior з методу GetControllerSessionBehavior (). Цей метод приймає в якості параметрів об'єкт RequestContext і рядок, що містить ім'я контролера. Можна повертати будь-який з чотирьох значень, описаних в таблиці вище і різні контролери можуть повертати різні значення.
У прикладі нижче приведена змінена реалізація методу GetControllerSessionBehavior () з класу CustomControllerFactory, який був створений раніше:
Управління станом сеансу з використанням DefaultControllerFactory
У разі використання вбудованої фабрики контролерів станом сеансу контролера можна управляти за рахунок застосування атрибута SessionState до індивідуальних класах контролерів, як показано в прикладі нижче, де створено новий контролер по імені FastController:
Атрибут SessionState застосовується до класу контролера і впливає на всі методи дії в цьому контролері. Єдиний параметр, який приймає цим атрибутом - це значення перерахування SessionStateBehavior. У наведеному прикладі стан сеансу повністю відключено. Це означає, що якщо спробувати встановити значення сеансу в контролері, наприклад:
або спробувати прочитати його в поданні:
то MVC Framework згенерує виняток, коли дія буде викликано або подання визуализировано. Коли станом сеансу є Disabled, властивість HttpContext.Session повертає null.
Використання асинхронних контролерів
Що лежить в основі MVC Framework платформа ASP.NET підтримує пул потоків .NET. який використовується для обробки клієнтських запитів. Цей пул називається пулом робочих потоків, а все потоки - відповідно, робітниками потоками. При отриманні запиту робочий потік витягується з пулу і починає обробляти запит. Після обробки запиту робочий потік повертається в пул, так що він буде доступний для обробки нових запитів у міру їх надходження.
Застосування пулу потоків в додатках ASP.NET забезпечує дві переваги:
За рахунок багаторазового використання робочих потоків вдається уникнути накладних витрат, пов'язаних зі створенням нового робочого потоку кожен раз, коли потрібно обробити запит.
За рахунок наявності фіксованої кількості доступних робочих потоків вдається уникнути ситуації, при якій число одночасно оброблюваних запитів перевищує можливості сервера.
Пул робочих потоків функціонує краще, коли запити можуть бути оброблені протягом короткого періоду часу. Така ситуація характерна для більшості додатків MVC. Однак при наявності дій, які залежать від інших серверів і вимагають тривалого часу на виконання, може виявитися, що всі робочі потоки зайняті очікуванням завершення роботи через інших систем.
Сервер здатний виконувати більше роботи (в кінці кінців, під час очікування потрібно дуже небагато ресурсів), але оскільки всі робочі потоки пов'язані, вхідні запити ставляться в чергу. Виникає досить дивна ситуація, коли додаток виглядає завислим, в той час як сервер в основному простоює.
У цей момент у вас може виникнути ідея про власну реалізації пулу робочих потоків, яка точно налаштована під конкретне застосування. Ні в якому разі не робіть так! Написання паралельного коду досить просто. Проте написання працездатного паралельного коду досить складно, особливо для новачків в паралельному програмуванні. Рекомендується дотримуватися стандартного пулу. Якщо ви маєте досвід паралельного програмування, то повинні знати, що переваги будуть дуже незначними в порівнянні із зусиллями з кодування і тестування нового пулу потоків.
Отже, рішення зазначеної проблеми полягає в застосуванні асинхронного контролера. Це збільшить загальну продуктивність програми, але не надасть будь-яких переваг щодо виконання асинхронних операцій.
Асинхронні контролери корисні тільки для дій, які пов'язані з введенням-виведенням або мережею, але не тих, які інтенсивно використовують центральний процесор. Проблема, яку ви намагаєтеся вирішити за допомогою асинхронних контролерів, пов'язана з невідповідністю між моделлю пулу і типом оброблюваних запитів. Пул призначений для гарантування того, що кожен запит отримує відповідну частку серверних ресурсів, але в підсумку виходить набір робочих потоків, які нічого не роблять. Якщо застосовувати додаткові фонові потоки для дій, які працювали з центральним процесором, то ресурси сервера будуть розтрачені на занадто велику кількість одночасно надходять запитів.
У цьому розділі передбачається, що ви знайомі з бібліотекою паралельних завдань (Task Parallel Library - TPL). Сама бібліотека описана в розділі Потоки і файли. також ви можете подивитися приклад створення асинхронної веб-форми на ASP.NET Web Forms.
підготовка прикладу
Перед тим, як приступити до пояснення асинхронних контролерів, ми продемонструємо приклад проблеми, для вирішення якої вони призначені. У прикладі нижче наведено код звичайного синхронного контролера по імені RemoteData, який був доданий в приклад проекту:
Цей контролер містить метод дії на ім'я Data (), який створює екземпляр класу моделі RemoteService і викликає на ньому метод GetRemoteData (). Даний метод є прикладом дії, що віднімає значний час, але мало що використовує центральний процесор. У прикладі нижче показаний клас RemoteService, який визначений в файлі RemoteService.cs всередині папки Models:
Насправді метод GetRemoteData (одночасно) відбувся земітовано. У реальності цей метод міг би отримувати складні дані через повільне з'єднання з мережею, але для простоти ми скористалися методом Thread.Sleep (), щоб емулювати двухсекундную затримку. Останнім додаванням є нове уявлення. Для цього створюється папка Views / RemoteData, в яку поміщається файл уявлення Data.cshtml з вмістом, показаним в прикладі:
Після запуску програми і переходу на URL виду / RemoteData / Data викликається відповідний метод дії, створюється об'єкт RemoteService і потім викликається метод GetRemoteData (). Через дві секунди (що імітують виконання реальної операції) повертаються дані з GetRemoteData (), які передаються поданням для візуалізації:
Проблема полягає в тому, що робочий потік, що обробляє запит, простоював дві секунди - він не робив нічого корисного і не був доступний для обробки інших запитів, поки перебував у стані очікування.
Створення асинхронного контролера
Завдяки використанню асинхронного контролера, робочий потік звільняється і може обробляти інші запити. Він не запобігає взаємодія користувача з інтерфейсом додатки протягом двосекундною очікування. Зрештою, фіктивні дані будуть отримані і оброблені. Існують технології клієнтської сторони (AJAX), за допомогою яких можна робити такі запити в браузері, що дозволяє, як мінімум, інформувати користувача про хід отримання даних і дати йому можливість продовжити роботу з іншою частиною програми.
Після ілюстрації проблеми, яку ми збираємося вирішити, давайте перейдемо до створення асинхронного контролера. Такий контролер можна створити двома способами. Перший з них - реалізація інтерфейсу IAsyncController знаходиться в просторі імен System.Web.Mvc.Async, який є асинхронним еквівалентом IController. Цей підхід демонструватися не буде, тому що він вимагає докладного пояснення концепцій паралельного програмування .NET.
Увага як і раніше буде зосереджено на MVC Framework, тому далі наводиться приклад другого підходу: застосування нових ключових слів await і async в звичайному контролері.
У попередніх версіях .NET Framework створення асинхронних контролерів було трудомістким завданням і вимагало успадкування контролера від спеціального класу і поділу кожної дії на два методу. Нові ключові слова await і async істотно спрощують цей процес: необхідно створити новий об'єкт Task і застосувати await до результату, як показано в прикладі нижче:
Старий спосіб створення асинхронних методів дій все ще підтримується, хоча розглянутий тут підхід набагато більш елегантний, тому рекомендується використовувати саме його. Один з артефактів старого підходу полягає в неможливості застосування для методів дій імен, які закінчуються на Async (наприклад, IndexAsync ()) або Completed (скажімо, IndexCompleted ()).
Отже, в цьому прикладі було проведено рефакторинг методу дії, так що він тепер повертає Task
Використання асинхронних методів в контролері
Асинхронний контролер можна також застосовувати для використання асинхронних методів в іншому місці програми. З метою демонстрації в клас RemoteService доданий асинхронний метод, як показано в прикладі нижче:
Результатом методу GetRemoteDataAsync () є об'єкт Task
Як бачите, обидва методи дій слідують одному і тому ж базовому шаблоном, а відмінність з'являється лише там, де створюється об'єкт Task. В результаті виклику будь-якого методу дії робочий потік не буде пов'язаний під час очікування завершення виклику GetRemoteData (). Це означає, що потік доступний для обробки інших запитів, тим самим значно покращуючи показники продуктивності додатка MVC Framework.