Web Forms 4

Встановлення атрибуту Async директиви Page у true повідомляє середовище ASP.NET, що ми хочемо використовувати асинхронну обробку запитів усередині веб-форми. Ця функція повинна включатись явно, т.к. встановлення та підтримка ресурсів, необхідних для асинхронних завдань, пов'язані з певними накладними витратами, які є бажаними, коли асинхронна обробка не потрібна.

В атрибуті AsyncTimeout вказано кількість секунд, протягом яких середовище ASP.NET чекатиме завершення асинхронних завдань перед тим, як виникне тайм-аут запиту. У прикладі він був встановлений у 60 секунд; якщо атрибут AsyncTimeout не вказаний, для нього приймається стандартне значення 45 секунд. Одна з причин застосування асинхронної обробки запитів пов'язана з необхідністю узгодження з тривалими операціями, тому ви повинні налаштувати значення AsyncTimeout так, щоб дозволити роботі успішно завершитися.

Асинхронна обробка реалізована у файлі відокремленого коду Default.aspx.cs, вміст якого наведено у прикладі нижче:

Зміни виглядають дрібними, але виконується великий обсяг робіт. Незабаром ми пояснимо всі деталі, а поки що розглянемо, який отриманий результат. На малюнку нижче показано, що сталося після запуску програми та запиту веб-форми Default.aspx:

запитів

Загальний час, витрачений на обробку запиту, отримання даних від віддаленого веб-сервера та генерацію відповіді трохи збільшився порівняно з синхронним варіантом, розглянутим у попередній статті (це пов'язано в основному з варіюванням можливостей та маршрутизації в Інтернеті). Велика відмінність полягає в тому, що потік запиту більше не блокується під час очікування запиту HTTP до http://professorweb.ru для отримання відповіді.

За рахунок застосування асинхронної обробки до цієї веб-форми з'явилася можливість повернути потік запиту в пул на період 1.5 секунд, який був витрачений на отримання відповіді від сервера. Протягом цього часу цей потік був доступний для обробки інших запитів, що призвело до покращення загальної пропускної здатності програми.

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

Використання асинхронного методу

Перша зміна, яка була внесена до файлу Default.aspx.cs, стосувалася використання іншого методу класу WebClient:

Замість синхронного методу DownloadString() викликається асинхронний варіант DownloadStringTaskAsync(), який повертає об'єкт Task і може застосовуватись із ключовим словом await. Метод DownloadStringTaskAsync() дає той же результат, що і метод DownLoadString(), але працює асинхронним чином, не блокуючи потік запиту.

Створення власних асинхронних методів

Можна переписати будь-який метод, забезпечивши повернення з нього об'єкта Task і використання ключового слова async, але оскільки потік, як і раніше, повинен виконувати цей метод, виникає ризик перенесення тієї ж проблеми в інше місце. У широкому значенні існують два винятки з цього:

Перший виняток передбачає передачу запиту іншому,більш обмеженому пулу потоків, такому як пул, застосовуваний бібліотеці паралельних завдань (TPL). Пул потоків TPL використовує невелику кількість потоків і має засоби скорочення часу, яке потоки витрачають на блокування.

Небезпека тут у тому, що можна звільняти потік, щоб уникнути вичерпання пулу потоків підключень, але зрештою витратити пул меншого розміру. Наприклад, бібліотека TPL виділяє по одному потоку на кожен процесор, і навіть за наявності в ній розширених засобів легко отримати чергу робіт такої довжини, що HTTP-запити почнуть припинятися тайм-аутом.

Другий виняток пов'язаний із застосуванням низькорівневих оптимізацій, які збільшують ефективність, не пов'язуючи потоки. Хорошим прикладом можуть бути порти завершення вводу-виводу, які операційна система Windows реалізує для того, щоб дозволити керувати великими кількостями потоків вводу-виводу за допомогою невеликої кількості потоків. Цей засіб використовується класом WebClient, і він також застосовується деякими постачальниками підключень до баз даних.

Запам'ятайте як емпіричне правило: якщо ви не маєте суттєвого досвіду паралельного програмування, то повинні застосовувати асинхронну обробку запитів до веб-форм ASP.NET тільки з використанням класів .NET Framework, які надають асинхронні методи, такі як WebClient.

Створення та реєстрація асинхронного сторінкового завдання

Нам необхідно запакувати асинхронну роботу, призначену для виконання, щоб її можна було інтегрувати в життєвий цикл, визначений класом Page. Це робиться із застосуванням класу PageAsyncTask, конструктор якого приймає делегат, що повертає об'єкт Task. Ми використовуємо лямбда-вираз наступного виду:

Церозширене застосування синтаксису лямбда-виразів, що спрощує визначення фонової роботи, не вимагаючи створення окремого асинхронного методу. Компілятор C# виконує ряд "магічних" дій з ключовими словами async (яке застосовується до лямбда-виразу) та await (яке використовується як префікс при виклику методу класу WebClient). Результатом є делегат, який повертає об'єкт Task та викликає метод DownloadStringTaskAsync(), не блокуючи потік запиту.

Асинхронна дія реєструється за рахунок передачі об'єкта PageAsyncTask методу RegisterAsyncTask(), визначеному в класі Page:

Метод RegisterAsyncTask() реєструє асинхронний об'єкт, але його виконання буде ініційовано лише після події PreRender та перед подією PreRenderComplete.

Виконання безлічі завдань

Можна зареєструвати кілька асинхронних завдань і всі вони будуть виконані після виникнення події PreRenderComplete - однак послідовно, а чи не паралельно. Це не блокує потік запиту, але не дає ефекту, який більшість людей очікують отримати, коли мають справу з безліччю асинхронних операцій.

Для демонстрації того, що станеться, ми додали нову веб-форму на ім'я Multiples.aspx, контент якої наведено в прикладі нижче:

Це варіація веб-форми Default.aspx, створеної раніше. За допомогою елемента керування Repeater відображається набір результатів. Крім того, визначено інший тип із властивостями, що стосуються цього прикладу - зокрема, нас цікавить час початку запиту до веб-сервера, а не проміжок часу, протягом якого потік запиту було заблоковано, або час виконання окремих запитів. Визначення типу та класу відокремленого коду представлені в прикладі нижче:

запиту

Виклик методуDataBind() гарантує відображення результатів усередині елемента керування Repeater. Причина пов'язана зі способом інтеграції елементів управління, що відображають дані, в цикл запиту Page.

За інформацією стовпці " Час початку " видно, що запити виконувались послідовно, тобто. запит до google.com не запускався, поки не пройшло близько 2.3 секунди після його отримання середовищем ASP.NET.

Це може бути прийнятним у вашій програмі, але в більшості програм потрібно поміщати роботу в чергу, щоб окремі запити можна було виконувати паралельно за наявності достатньої кількості доступних потоків. Досягти такого результату найпростіше за допомогою бібліотеки TPL - у прикладі нижче показано, як це зроблено у файлі відокремленого коду Multiples.aspx.cs:

Ми реєструємо лише один об'єкт PageAsyncTask та керуємо паралельними завданнями самостійно із застосуванням класу Task. Як згадувалося у попередній статті, бібліотека TPL є окремою великою темою, і ми не збираємося тут докладно пояснювати цей код. Зазначимо тільки, що в результаті забезпечується розміщення в чергу окремих запитів до контенту веб-сайту, що дозволить їх паралельно:

запиту

Як бачите, всі три запити були запущені майже в один і той же час. Не гарантовано станеться, т.к. у черзі можуть бути інші роботи, а всі потоки TPL можуть бути зайняті, але якщо є вільні системні ресурси, то час, витрачений на обробку запитів, буде набагато меншим.