Разработка сайта для Вашего бизнеса. Веб дизайн. Дизайн логотипа, фирменного стиля, рекламная фотография . Комплексный рекламный креатив.

Ralex. We do the work.
На рынке с 1999го года. Средняя ценовая категория. Ориентация на эффективность решений.
Ознакомтесь с нашим портфолио
Узнайте больше о услугах
Свяжитесь с нами:
E-mail: [email protected]
Tel: (044) 587 - 84 - 78
Custom web design & дизайн и разработка сайта "под ключ"
Креативный, эффективный дизайн. Система управления сайтом (СУС).
Custom flexible разработка систем электронной коммерции
Система e-commerce разрабатывается под индивидуальные потребности. Гибкая функциональность.
Search Engine Optimzation & оптимизация под поисковые системы (SEO)
Постоянная оптимизация и мониторинг сайта в поисковых системах. Достигаем результата быстро и эффективно
Custom logo design & дизайн логотипа и фирменного стиля
Многолетний опыт. Огромное портфолио. Уникальное предложение и цена.
профессиональная рекламная фотография
креативно, смело, качественно
Custom logo design & рекламный креатив. дизайн рекламы
Многолетний опыт. Огромное портфолио. Уникальное предложение и цена.

Асинхронне програмування в C # 5

  1. Автор: Тепляков Сергій Володимирович джерело: RSDN Magazine # 4-2010
  2. Синхронне і асинхронне виконання операцій
  3. Асинхронне програмування в C # 5
  4. висновок
Автор: Тепляков Сергій Володимирович
джерело: RSDN Magazine # 4-2010
Опубліковано: 05.02.2011
Виправлено: 10.12.2016
Версія тексту: 1.1

Наша взаимовыгодная связь https://banwar.org/

Всі знають, що синхронне виконання тривалих операцій - це погано. Це погано з багатьох причин, починаючи з того, що синхронні виклики призводять до підвисання призначеного для користувача інтерфейсу, поганий масштабованості і розширюваності, закінчуючи тим, що ви не зможете скористатися всіма можливостями многоядерного процесора навіть вашого домашнього комп'ютера, не кажучи вже про переваги асинхронного введення / виводу , ефективність якого проявляється навіть на одноядерних процесорах. Про тему асинхронности і багатопоточності говорять на кожному кроці, починаючи від відомих авторів на кшталт Джеффрі Ріхтера, Джо Даффі або Джозефа Албахарі, і закінчуючи безліччю статей на кожному другому технічному сайті і обов'язковим питанням на співбесіді типу «А скільки ви знаєте способів виконати операцію асинхронно?» .

Однією з причин такого стану справ є те, що вся підтримка багатопоточності в мові C # закінчується оператором lock, а все інше прикручено до нього за допомогою бібліотек, починаючи з BCL і RX, закінчуючи Power Threading і іншими "велосипедами" різного виду і форми (хоча потрібно визнати, що головна причина полягає в тому, що теми многопоточности і асинхронности самі по собі досить складні, і всі спроби їх спростити настільки, щоб вони були зрозуміла домогосподаркам, успіхом не увінчалися і чи увінчаються коли-небудь). Однак, як говорилося в нещодавній доповіді Андерса Хейлсберг на конференції Microsoft PDC, щастя має настати з виходом нової версії мови C #, в якому робота з асинхронними операціями буде додана на рівні мови. Однак перш ніж приступати до розгляду можливостей, які в черговий раз повинні спростити рішення асинхронних завдань, слід ближче розглянути проблеми, які виникають при використанні синхронних операцій і те, як ці проблеми вирішувалися до сих пір.

Синхронне і асинхронне виконання операцій

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

Дуже популярним прикладом виконання асинхронних операцій є звернення до якого-небудь зовнішнього ресурсу типу Web-сервісу, доступ до Web-сторінці або асинхронний доступ до файлу. І хоча в якості прикладу з тим же успіхом можна використовувати метод з тілом виду Sleep (5000), зробимо вигляд, що ми намагаємося вирішити справжню завдання, наприклад, написати свій власний Web-браузер з підтримкою декількох вкладок.

Отже, давайте розглянемо синхронну версію функції, яка звертається до заданих Web-сторінок і виводить на екран довжини отриманих відповідей (не дуже функціонально для справжнього Web-браузера, але треба ж з чогось починати):

static void SyncVersion () {Stopwatch sw = Stopwatch.StartNew (); string url1 = "http://rsdn.ru"; string url2 = "http://gotdotnet.ru"; string url3 = "http://blogs.msdn.com"; var webRequest1 = WebRequest.Create (url1); var webResponse1 = webRequest1.GetResponse (); Console.WriteLine ( "{0}: {1}, elapsed {2} ms", url1, webResponse1.ContentLength, sw.ElapsedMilliseconds); var webRequest2 = WebRequest.Create (url2); var webResponse2 = webRequest2.GetResponse (); Console.WriteLine ( "{0}: {1}, elapsed {2} ms", url2, webResponse2.ContentLength, sw.ElapsedMilliseconds); var webRequest3 = WebRequest.Create (url3); var webResponse3 = webRequest3.GetResponse (); Console.WriteLine ( "{0}: {1}, elapsed {2} ms", url3, webResponse3.ContentLength, sw.ElapsedMilliseconds); } ПРИМІТКА

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

У синхронного виконання операцій, подібних читання Web-сторінок, є ряд недоліків: по-перше, поточний потік буде блокований на невизначений термін, а адже цей потік цілком може виявитися потоком користувальницького інтерфейсу, і наше додаток буде виглядати чудовим біленьким віконцем з пісочним годинником посередині . Крім того, якщо кожен запит буде тривати 5 секунд, то три запити будуть виконуватися 15 секунд, в той час, як при паралельному виконанні все три запити можуть бути виконані за 5 секунд. Звичайно, цього ніколи не буде, особливо при зверненні до Web-сторінок, оскільки частина цього часу витрачається на передачу даних по разделяемому каналу, що призведе до збільшення часу виконання кожного паралельного запиту, але у запиту є і постійна складова, яка не буде збільшуватися при одночасному виконанні декількох запитів. І хоча про конкретні цифри збільшення ефективності паралельного виконання подібних операцій можна сперечатися дуже довго, можна з упевненістю сказати, що асинхронне виконання операцій, що інтенсивно використовують введення / виведення (IO Bound), операція вигідна з точки зору як ефективності, так і масштабованості.

Оскільки ідея асинхронного виконання далеко не нова, класи, подібні WebRequest (і його спадкоємцю HttpWebRequest) підтримують спеціальний набір функцій, які дозволяють виконувати тривалі операції асинхронно за допомогою методів BeginGetResponse і EndGetResponse. Традиційно така модель називається APM - Asynchronous Programming Model.

static void SimpleApm () {string url1 = "http://rsdn.ru"; string url2 = "http://gotdotnet.ru"; string url3 = "http://blogs.msdn.com"; var webRequest1 = WebRequest.Create (url1); webRequest1.BeginGetResponse (ProcessWebRequest, webRequest1); var webRequest2 = WebRequest.Create (url2); webRequest2.BeginGetResponse (ProcessWebRequest, webRequest2); var webRequest3 = WebRequest.Create (url3); webRequest3.BeginGetResponse (ProcessWebRequest, webRequest3); } Static void ProcessWebRequest (IAsyncResult ar) {var webRequest = (WebRequest) ar.AsyncState; var webResponse = webRequest.EndGetResponse (ar); Console.WriteLine ( "{0}: {1}", webRequest.RequestUri, webResponse.ContentLength); }

Тепер основний потік виконує тільки запити на виконання асинхронних операцій, а самі операції виконуються в робочих потоках пулу потоків і більшу частину часу сплять, чекаючи завершення операцій введення / виводу. Це дозволяє запустити практично одночасно всі три запиту введення / виводу і незалежно очікувати завершення кожного з них. Цей код виглядає досить просто, проте спроба зберегти результати всіх трьох асинхронних операцій в файл (причому теж асинхронно) з повноцінною обробкою помилок, зробить код значно складнішим для розуміння і супроводу. Крім того, додавання функцій зворотного виклику для обробки результатів асинхронних операцій змінює потік управління таким чином, що стають недоступні такі зручні допоміжні мовні конструкції, як блоки try / catch / finally, lock або using.

Асинхронне програмування в C # 5

Ідея, яка лежить в основі асинхронних операцій в C # 5, дуже схожа на ту, яку використовував Джеффрі Ріхтер в своєму класі AsyncEnumerator, тільки на цей раз, крім нас з вами і старовини Ріхтера, про неї дізнався ще і компілятор (що здорово позначається на простоті використання цих синтаксичних конструкцій). Тепер давайте візьмемо нашу синхронну версію, яку розглянуто в попередньому розділі, і зробимо з неї асинхронну за допомогою нових можливостей мови C #.

ПРИМІТКА

На даний момент ніхто не знає, коли буде доступна нова версія мови програмування C # 5. Всі приклади, наведені в цій статті, ґрунтуються на CTP (Community Technology Preview) версії, представленої Андерсом Хейлсберг на конференції Microsoft PDC в жовтні 2010 року.

Перше, що потрібно зробити, це змінити оголошення нашої функції таким чином:

static async Task AsyncVersion ()

Ключове слово async (яке в CTP версії є ключовим словом, а в остаточній версії мови буде контекстним ключовим словом) в сигнатурі методу говорить про те, що цей метод виконується асинхронно і повертає управління викликає коду відразу після початку деякої асинхронної операції. «Асинхронні методи» можуть повертати один з трьох типів повертається: void, Task і Task <T>. Якщо метод повертає void, то це буде асинхронна операція типу: «запустили і забули», оскільки обробити результат цієї операції буде неможливо. Прикладом такої операції може служити асинхронне повідомлення всіх віддалених абонентів про будь-яку подію, якщо вам не важливо, отримають вони ці повідомлення чи ні. З класами Task і Task <T> читач, може бути, знаком, оскільки вони доступні в .Net Framework починаючи з 4 версії. Основна ідея цих класів полягає в тому, що вони інкапсулюють в собі «незавершену завдання», і ми можемо дочекатися її завершення, встановити «продовження» (щось, що повинно бути викликано при завершенні цього завдання) і т.п. При цьому клас Task <T> є спадкоємцем класу Task і відрізняється від останнього тим, що дозволяє отримати значення, що повертається типу T за допомогою властивості Result, в той час, як клас Task говорить про те, що деяка задача повертає void, і цінна за рахунок своїх побічних ефектів.

Оскільки ми хочемо виконати набір асинхронних операцій, які сумарно нічого не повертають, а цінні саме за рахунок побічних ефектів, то нам достатньо скористатися типом Task як значення, що повертається. Якби ми хотіли не просто вивести результати на консоль, а, наприклад, повернути строкове представлення всіх результатів, то могли б використовувати значення, що повертається типу Task <string>.

Тепер, коли ми розібралися з типом значення, що повертається, потрібно змінити і саме тіло методу. Для цього такі рядки:

var webResponse1 = webRequest1.GetResponse ();

Потрібно замінити на:

var webResponse1 = await webRequest1.GetResponseAsync ();

Ключове слово await (в остаточній версії мови це теж буде контекстним ключовим словом) зовсім не означає, що ми припинимо виконання в цій точці коду до тих пір, поки запитана асинхронна операція не буде виконана. Це може збивати з пантелику, але це означає в точності протилежну ідею. Ключове слово await означає, що після початку асинхронної операції (в даному випадку після початку асинхронної операції отримання WebResponse) метод поверне управління, а продовжить його з цього ж місця вже після завершення асинхронної операції.

Ідея, яка лежить в основі реалізації цієї можливості мови, аналогічна реалізації блоків ітераторів в мові C #: в обох випадках компілятор генерує кінцевий автомат, який відстежує, де було перервано виконання методу, і продовжує виконання з цієї ж точки при наступному виклику. Однак якщо в разі ітераторів відновлення виконання блоку ітераторів відбувається при наступному виклику методу MoveNext зовнішнім кодом (наприклад, foreach), то асинхронний метод залишиться активним автоматично після завершення асинхронної операції. Для цього для новоствореної завдання як «продовження» встановлюється цей же метод (точніше, та частина коду методу, яка йде безпосередньо за точкою виклику), який і буде викликаний після завершення цього завдання.

Тепер давайте подивимося на змінений метод цілком:

static async Task AsyncVersion () {Stopwatch sw = Stopwatch.StartNew (); string url1 = "http://rsdn.ru"; string url2 = "http://gotdotnet.ru"; string url3 = "http://blogs.msdn.com"; var webRequest1 = WebRequest.Create (url1); Console.WriteLine ( "Перед викликом webRequest1.GetResponseAsync (). Thread Id: {0}", Thread.CurrentThread.ManagedThreadId); var webResponse1 = await webRequest1.GetResponseAsync (); Console.WriteLine ( "{0}: {1}, elapsed {2} ms. Thread Id: {3}", url1, webResponse1.ContentLength, sw.ElapsedMilliseconds, Thread.CurrentThread.ManagedThreadId); var webRequest2 = WebRequest.Create (url2); Console.WriteLine ( "Перед викликом webRequest2.GetResponseAsync (). Thread Id: {0}", Thread.CurrentThread.ManagedThreadId); var webResponse2 = await webRequest2.GetResponseAsync (); Console.WriteLine ( "{0}: {1}, elapsed {2} ms. Thread Id: {3}", url2, webResponse2.ContentLength, sw.ElapsedMilliseconds, Thread.CurrentThread.ManagedThreadId); var webRequest3 = WebRequest.Create (url3); Console.WriteLine ( "Перед викликом webRequest3.GetResponseAsync (). Thread Id: {0}", Thread.CurrentThread.ManagedThreadId); var webResponse3 = await webRequest3.GetResponseAsync (); Console.WriteLine ( "{0}: {1}, elapsed {2} ms. Thread Id: {3}", url3, webResponse3.ContentLength, sw.ElapsedMilliseconds, Thread.CurrentThread.ManagedThreadId); }

І ось як цей метод викликається:

static void Main (string [] args) {try {Console.WriteLine ( "Id основного потоку: {0}", Thread.CurrentThread.ManagedThreadId); var task = AsyncVersion (); Console.WriteLine ( "Метод AsyncVersion () повернув управління"); task.Wait (); Console.WriteLine ( "Асинхронна операція завершена!"); } Catch (System.AggregateException e) {Console.WriteLine ( "AggregateException: {0}", e.InnerException.Message); }

А ось результат його виконання:

Id основного потоку: 10 Перед викликом webRequest1.GetResponseAsync (). Thread Id: 10 Метод AsyncVersion () Повернув управління http://rsdn.ru: 1672, elapsed 657ms. Thread Id: 13 Перед викликом webRequest2.GetResponseAsync (). Thread Id: 13 http://gotdotnet.ru: 99470, elapsed 1915ms. Thread Id: 14 Перед викликом webRequest3.GetResponseAsync (). Thread Id: 14 http://blogs.msdn.com: 47927, elapsed 2628ms. Thread Id: 15 Асинхронная операція завершена!

А тепер давайте розберемо детально, що відбувається всередині цього коду. Виклик методу AsyncVersion відбувається в поточному потоці (ми бачимо, що перед викликом методу webReqest1.GetResponseAsync ідентифікатор потоку дорівнює 10, тобто дорівнює ідентифікатору основного потоку), і управління з методу AsyncVersion повертається відразу ж після першого оператора await, а не після завершення першій асинхронної операції. Ця функція повертає об'єкт типу Task, щоб ми змогли дочекатися завершення операції і обробити її результати. Перш ніж повернути управління зухвалому коду, метод AsyncVersion встановлюється в якості «продовження» поточного завдання, і запам'ятовується місце, з якого потрібно продовжити виконання. Потім, після завершення першої асинхронної операції, виконання цього методу відновлюється з місця, безпосередньо йде за асинхронним викликом, і, як ми бачимо, вже в іншому потоці. Далі, цей процес триває до тих пір, поки не буде завершена третя асинхронна операція, після чого метод task.Wait поверне управління, і ми побачимо на консолі заповітне: "Асинхронна операція завершена!".

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

Обробка помилок також зазнала деяких змін, але також дуже незначних. Якщо ви вже знайомі з TPL (Task Parallel Library), яка входить до складу .Net Framework починаючи з версії 4.0, то клас System.AggregateExcpetionдолжен бути вам вже знаком. Цей клас «збирає» все виключення, що відбулися в усіх асинхронних операціях, і накопичує їх у себе всередині. Причина цього полягає в тому, що у одного завдання може бути десяток дочірніх завдань, кожна з яких може містити ще кілька «подзадач», і кожне завдання з цього «дерева» завдань може завершитися невдало. Звичайно, обробка виключень дещо ускладнилася, але зовсім мало в порівнянні з тими проблемами, які виникають при обробці помилок в класичній моделі APM.

Тепер давайте спробуємо ускладнити нашу вихідну задачу і додамо асинхронне збереження всіх отриманих результатів в файл. Оскільки тепер наш код і так містить два типи асинхронних операцій, то все url-адреси ми помістимо в масив і скористаємося LINQ для отримання набору відповідей від відповідних Web-вузлів.

static async void AsyncVersionWithSaving () {Stopwatch sw = Stopwatch.StartNew (); var urls = new string [] { "http://rsdn.ru", "http://gotdotnet.ru", "http://blogs.msdn.com"}; var tasks = (from url in urls let webRequest = WebRequest.Create (url) select new {Url = url, Response = webRequest.GetResponseAsync ()}). ToList (); var data = await TaskEx.WhenAll (tasks.Select (t => t.Response)); var sb = new StringBuilder (); foreach (var s in tasks) {sb.AppendFormat ( "{0}: {1}, elapsed {2} ms. Thread Id: {3}", s.Url, s.Response.Result.ContentLength, sw.ElapsedMilliseconds , Thread.CurrentThread.ManagedThreadId) .AppendLine (); } Var outputText = sb.ToString (); Console.WriteLine ( "Web request results: {0}", outputText); using (var fs = new FileStream ( "d: \\ results.txt", FileMode.Create, FileAccess.Write, FileShare.Write)) {byte [] data = UnicodeEncoding.Default.GetBytes (outputText); await fs.WriteAsync (data, 0, data.Length); }}

Ось це вже дійсно цікаво! Ми отримали повністю асинхронний код, але при цьому він залишився таким же читабельним, як і синхронна версія; він містить простий і зрозумілий потік виконання, звичні конструкції, що забезпечують коректну роботу з ресурсами (мова йде про конструкції using), а для обробки помилок, досить виклик цього методу обернути в блок try / catch і перехопити AggregateException.

висновок

Отже, що ми отримали в результаті? У новій версії мови C # нас чекають нові можливості, якими дійсно зручно користуватися без остраху допустити прості і дурні помилки. При цьому переклад синхронної версії коду в асинхронну виконується досить просто, потік керування не вивертається навиворіт, як при використанні класичної моделі асинхронного програмування, а код залишається таким же простим і зрозумілим.

Ця стаття опублікована в журналі RSDN Magazine # 4-2010. Інформацію про журнал можна знайти тут
Категории
  • Биология
  • Математика
  • Краеведению
  • Лечебная
  • Наука
  • Физике
  • Природоведение
  • Информатика
  • Новости

  • Новости
    https://banwar.org/
    Наша взаимовыгодная связь https://banwar.org/. Запустив новый сайт, "Пари Матч" обещает своим клиентам незабываемый опыт и возможность выиграть крупные суммы.


    Наши клиенты
    Клиенты

    Быстрая связь

    Тел.: (044) 587-84-78
    E-mail: [email protected]

    Имя:
    E-mail:
    Телефон:
    Вопрос\Комментарий: