Light-electric.com

IT Журнал
1 просмотров
Рейтинг статьи
1 звезда2 звезды3 звезды4 звезды5 звезд
Загрузка...

Асинхронное программирование это

Асинхронное программирование. Часть 1: Как работает процессор

Процессоры могут выполнять программы асинхронно. Объясняем, как это происходит, зачем нужно и что значит для программирования.

Асинхронность, многопоточность и параллелизм — очень важные аспекты программирования, без которых сложно представить современные компьютеры.

Язык программирования C# предоставляет разработчикам множество инструментов для создания приложений, которые могут работать с большим количеством потоков, выполняя их параллельно и асинхронно. Это одна из причин популярности этого языка, а также главная причина, почему именно он выбран для этой серии статей.

Сразу разобраться в этих концепциях сложно, поэтому мы начнём с небольшого ликбеза о том, как компьютерные процессоры выполняют операции.

ВНИМАНИЕ!

Информация в следующих разделах сильно упрощена.

Евгений Кучерявый

Пишет о программировании, в свободное время создает игры. Мечтает открыть свою студию и выпускать ламповые RPG.

Как работает процессор

За единицу времени (она называется тик) процессор может выполнить только одну задачу — например, прочитать значение ячейки памяти. Поэтому на то, чтобы выполнить следующие операции, потребуется 4 тика:

  1. Прочитать данные из двух ячеек.
  2. Сложить их.
  3. Записать результат в другую ячейку.

Это касается только атомарных операций, то есть самых маленьких и неделимых. Более сложные задачи могут состоять из нескольких атомарных. Например, чтобы провести умножение числа A на число B, нужно будет прибавить к числу A само себя B — 1 раз.

5 * 5 = 5 + 5 + 5 + 5 + 5

Ещё больше тиков потребуется на деление, а если операцию нужно провести над числами с плавающей запятой, то даже представить страшно, сколько их нужно.

Прямо сейчас у вас могут быть открыты десяток вкладок в браузере, плеер, мессенджер, редактор кода и ещё много всего. Поэтому кажется странным, что ничего из этого на самом деле не работает одновременно.

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

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

Количество тиков измеряется в герцах (Гц) — это единица измерения частоты протекания периодических процессов. Например, если мимо вашего дома раз в секунду проезжает гоночный болид, то его частота будет равна 1 Гц. Если болид проезжает два раза в секунду, то его частота — 2 Гц; если он проезжает трижды, то давно пора подумать о переезде.

Процессор так быстро выполняет процессы, что его частота измеряется в гигагерцах.

1 ГГц = 1 000 000 000 Гц

Частота современных процессоров обычно равна 2-3 ГГц.

Как процессор выполняет программы

Каждая программа состоит из множества процессов: нужно 500 раз провести сложение, записать данные в 2000 ячеек, перемножить всё это, а потом, наконец, поделить.

Если выполнять всё это линейно, то программы станут очень неудобными. Например, когда вы будете нажимать на кнопку скачивания в браузере, ваш компьютер будет блокироваться до тех пор, пока скачивание не завершится. Хорошо, если вы хотя бы будете видеть, сколько процентов загрузилось.

Тогда код программы будет выглядеть примерно так:

Пока выполняется этот цикл, вы не сможете сделать ничего, что не прописано внутри него. И, как видите, здесь нет строчек вроде проигрывать музыку или листать ленту. Более того, даже строчек для отслеживания движений мышью нет.

Поэтому, чтобы компьютерами было удобно пользоваться, мы делим все выполняемые программы на потоки. Допустим, у нас запущено 10 программ, а процессор работает на скорости 100 тиков в секунду.

Тогда каждый поток получит по 10 тиков. То есть процессор будет в течение 10 тиков выполнять инструкции от одного потока, а потом переходить к инструкциям другого — и так по кругу. Также у каждого потока есть приоритет: более важные программы будут получать больше тиков.

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

Когда нужна асинхронность

Чаще всего асинхронность нужна в программах с графическим интерфейсом. В них основная логика и работа с изображением помещены в разные потоки. Поэтому, когда логика занята, вы всё ещё можете пользоваться приложением.

Если же всё это выполняется в одном потоке, то приложение будет подвисать, когда выполняется сложная инструкция. В ОС Windows часто можно заметить, что, когда приложение что-то делает, а вы кликаете на него, то в заголовке окна можно увидеть словосочетание «Не отвечает».

Это не всегда означает, что приложение зависло. Возможно, оно просто занято решением какой-то сложной задачи, которая выполняется в том же потоке.

Пример асинхронного приложения

Чтобы лучше закрепить тему, давайте напишем приложение с использованием асинхронности. Оно будет разделено на два потока (Thread): первый будет обновлять полосу загрузки, а второй — выполнять саму загрузку.

В следующих статьях вы узнаете, как всё устроено, а пока посмотрим, как это работает:

Здесь можно заметить, что курсор обновляется чаще (каждые 100 мс), чем проценты (каждые 500 мс), как и было записано в коде программы.

Заключение

Асинхронность, многопоточность и параллелизм — очень мощные и полезные инструменты, которые каждый день делают нашу жизнь лучше. Однако они хранят в себе массу опасностей, которые могут стать причиной катастрофы (буквально), — этому будет посвящена отдельная статья серии.

Учитывая сложность этих инструментов, понадобится не одна статья, чтобы их объяснить. Я постараюсь не только показать, как их использовать, но и помочь на более глубоком уровне понять принципы их работы.

Многое об АМП вы можете узнать на нашем курсе «Профессия C#-разработчик». Там вы освоите все инструменты, необходимые программисту на C#, чтобы постепенно стать профессионалом.

Асинхронность в программировании

    Статьи, 20 декабря 2018 в 18:04

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

При вызове методов read() и write() текущий поток исполнения будет прерван в ожидании ввода-вывода по сети. Причём большую часть времени программа будет просто ждать. В высоконагруженных системах чаще всего так и происходит — почти всё время программа чего-то ждёт: диска, СУБД, сети, UI, в общем, какого-то внешнего, независимого от самой программы события. В малонагруженных системах это можно решить созданием нового потока для каждого блокирующего действия. Пока один поток спит, другой работает.

Но что делать, когда пользователей очень много? Если создавать на каждого хотя бы один поток, то производительность такого сервера резко упадёт из-за того, что контекст исполнения потока постоянно сменяется. Также на каждый поток создаётся свой контекст исполнения, включая память для стека, которая имеет минимальный размер в 4 КБ. Эту проблему может решить асинхронное программирование.

Асинхронность

Асинхронность в программировании — выполнение процесса в неблокирующем режиме системного вызова, что позволяет потоку программы продолжить обработку. Реализовать асинхронное программирование можно несколькими способами, о которых вы узнаете ниже.

Callbacks

Для написания асинхронной программы можно использовать callback-функции (от англ. callback — обратный вызов) — функции, которые будут вызваны асинхронно каким-либо обработчиком событий после завершения задачи. Переписанный пример сервера на callback-функциях:

В wait_connection() мы всё ещё ждём чего-то, но теперь вместе с этим внутри функции wait_connection() может быть реализовано подобие планировщика ОС, но с callback-функциями (пока мы ждём нового соединения, почему бы не обработать старые? Например, через очередь). Callback-функция вызывается, если в сокете появились новые данные — лямбда в async_read() , либо данные были записаны — лямбда в async_write() .

Читать еще:  Программирование в эксель для чайников

В результате мы получили асинхронную работу нескольких соединений в одном единственном потоке, который намного реже будет ждать. Эту асинхронность можно также распараллелить, чтобы получить полный профит от утилизации процессорного времени.

У такого подхода есть несколько проблем. Первую в шутку называют callback hell. Достаточно погуглить картинки на эту тему, чтобы понять, насколько это нечитаемо и некрасиво. В нашем примере всего две вложенные callback-функции, но их может быть намного больше.

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

Async/Await

Попробуем сделать асинхронный код так, чтобы он выглядел как синхронный. Для большего понимания немного поменяем задачу: теперь нам необходимо прочитать данные из СУБД и файла по ключу, переданному по сети, и отправить результат обратно по сети.

Пройдём по программе построчно:

  • Ключевое слово async в заголовке функции говорит компилятору, что функция асинхронная и её нужно компилировать по-другому. Каким именно образом он будет это делать, написано ниже.
  • Первые три строки функции: создание и ожидание соединения.
  • Следующая строка делает асинхронное чтение, не прерывая основной поток исполнения.
  • Следующие две строки делают асинхронный запрос в базу данных и чтение файла. Оператор await приостанавливает текущую функцию, пока не завершится выполнение асинхронной задачи чтения из БД и файла.
  • В последних строках производится асинхронная запись в сокет, но лишь после того, как мы дождёмся асинхронного чтения из БД и файла.

Это быстрее, чем последовательное ожидание сначала БД, затем файла. Во многих реализациях производительность async / await лучше, чем у классических callback-функций, при этом такой код читается как синхронный.

Корутины

Описанный выше механизм называется сопрограммой. Часто можно услышать вариант «корутина» (от англ. coroutine — сопрограмма).

Далее будут описаны различные виды и способы организации сопрограмм.

Несколько точек входа

По сути корутинами называются функции, имеющие несколько точек входа и выхода. У обычных функций есть только одна точка входа и несколько точек выхода. Если вернуться к примеру выше, то первой точкой входа будет сам вызов функции оператором asynс , затем функция прервёт своё выполнение вместо ожидания БД или файла. Все последующие await будут не запускать функцию заново, а продолжать её исполнение в точке предыдущего прерывания. Да, во многих языках в корутине может быть несколько await ’ов.

Для большего понимания рассмотрим код на языке Python:

Программа выведет всю последовательность чисел факториала с номерами от 0 до 41.

Функция async_factorial() вернёт объект-генератор, который можно передать в функцию next() , а она продолжит выполнение корутины до следующего оператора yield с сохранением состояния всех локальных переменных функции. Функция next() возвращает то, что передаёт оператор yield внутри корутины. Таким образом, функция async_factorial() в теории имеет несколько точек входа и выхода.

Stackful и Stackless

В зависимости от использования стека корутины делятся на stackful, где каждая из корутин имеет свой стек, и stackless, где все локальные переменные функции сохраняются в специальном объекте.

Так как в корутинах мы можем в любом месте поставить оператор yield , нам необходимо где-то сохранять весь контекст функции, который включает в себя фрейм на стеке (локальные переменные) и прочую метаинформацию. Это можно сделать, например, полной подменой стека, как это делается в stackful корутинах.

На рисунке ниже вызов async создаёт новый стек-фрейм и переключает исполнение потока на него. Это практически новый поток, только исполняться он будет асинхронно с основным.

yield в свою очередь возвращает обратно предыдущий стек-фрейм на исполнение, сохраняя ссылку на конец текущего в предыдущий стек.

Наличие собственного стека позволяет делать yield из вложенных вызовов функций, но такие вызовы сопровождаются полным созданием/сменой контекста исполнения программы, что медленней, чем stackless корутины.

Более производительными, но вместе с тем и более ограниченными, являются stackless корутины. Они не используют стек, и компилятор преобразует функцию, содержащую корутины, в конечный автомат без корутин. Например, код:

Будет преобразован в следующий псевдокод:

По сути здесь создаётся класс, который сохраняет всё состояние функции, а также последнюю точку вызова yield . У такого подхода есть проблема: yield может быть вызван только в теле функции-корутины, но не из вложенных функций.

Симметричные и асимметричные

Корутины также делятся на симметричные и асимметричные.

Симметричные имеют глобальный планировщик корутин, который и выбирает среди всех ожидающих асинхронных операций ту, которую стоит выполнить следующей. Примером является планировщик, о котором говорилось в начале функции wait_connection() .

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

Вывод

Асинхронное программирование является очень мощным инструментом для оптимизации высоконагруженных программ с частым ожиданием системы. Но, как и любую сложную технологию, её нельзя использовать только потому, что она есть. Необходимо всегда задавать себе вопрос: а нужна ли мне эта технология? Какую практическую пользу она мне даст? Иначе разработчики рискуют потратить очень много сил, времени и денег, не получив никакого профита.

Понимание асинхронного программирования

Node Hero: Глава 3

В этой главе я расскажу вам о принципах асинхронного программирования и покажу, как создавать асинхронные операции в JavaScript и Node.js.

Синхронное программирование

В традиционной практике программирования большинство операций ввода-вывода происходит синхронно. Если вы задумайтесь о Java и о том, как вы читаете файл в ней, вы получите что-то вроде этого:

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

Если у вас больше блокирующих операций, очередь событий становится ещё хуже:

Для решения этой проблемы Node.js предлагает модель асинхронного программирования.

Асинхронное программирование в Node.js

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

В следующем примере я покажу простой процесс чтения файлов в Node.js, как синхронным, так и асинхронным способом, с целью показать вам, чего вы можете достигнуть, если будете избегать блокировки ваших приложений.

Начнем с простого примера: синхронное чтение файла с использованием Node.js:

Что здесь происходит? Мы читаем файл, используя синхронный интерфейс модуля fs . Он работает ожидаемым образом: в переменную content сохраняется содержимое file.md . Проблема с этим подходом заключается в том, что Node.js будет заблокирована до завершения операции, то есть, пока читается файл, она не может сделать ничего полезного.

Посмотрим, как мы можем это исправить!

Асинхронное программирование, в том виде, в каком мы знаем его в JavaScript, может быть реализовано только при условии, что функции являются объектами первого класса: они могут передаваться как любые другие переменные другим функциям. Функции, которые могут принимать другие функции в качестве аргументов, называются функциями высшего порядка.

Один из самых простых примеров функций высшего порядка:

Читать еще:  Программирование макросов на vba

В приведенном выше примере мы передаем функцию isBiggerThanTwo в функцию filter . Таким образом, мы можем определить логику фильтрации.

Так появились функции обратного вызова (колбеки): если вы передаете функцию другой функции в качестве параметра, вы можете вызвать её внутри функции, когда она закончит свою работу. Нет необходимости возвращать значения, нужно только вызывать другую функцию с этими значениями.

В основе Node.js лежит принцип «первым аргументом в колбеке должна быть ошибка». Его придерживаются базовые модули, а также большинство модулей, найденных в NPM.

Что следует здесь выделить:

  • обработка ошибок: вместо блока try-catch вы проверяете ошибку в колбеке
  • отсутствует возвращаемое значение: асинхронные функции не возвращают значения, но значения будут переданы в колбеки

Давайте немного изменим этот файл, чтобы увидеть, как это работает на практике:

Результатом выполнения этого кода будет:

Как вы можете видеть, как только мы начали читать наш файл, выполнение кода продолжилось, а приложение вывело end of the file . Наш колбек вызвался только после завершения чтения файла. Как такое возможно? Встречайте цикл событий (event loop).

Цикл событий

Цикл событий лежит в основе Node.js и JavaScript и отвечает за планирование асинхронных операций.

Прежде чем погрузиться глубже, давайте убедимся, что мы понимаем, что такое программирование с управлением по событиям (event-driven programming).

Программирование с управлением по событиям представляет собой парадигму программирования, в которой поток выполнения программы определяется событиями, такими как действия пользователя (щелчки мышью, нажатия клавиш), выходы датчиков или сообщения из других программ/потоков.

На практике это означает, что приложения реагируют на события.

Кроме того, как мы уже узнали в первой главе, с точки зрения разработчика Node.js является однопоточным. Это означает, что вам не нужно иметь дело с потоками и синхронизировать их, Node.js скрывает эту сложность за абстракцией. Всё, кроме кода, выполняется параллельно.

Для более глубокого понимания работы цикла событий рекомендуем посмотреть это видео:

Асинхронный поток управления

Поскольку теперь у вас есть общее представление о том, как работает асинхронное программирование в JavaScript, давайте рассмотрим несколько примеров того, как вы можете организовать свой код.

Async.js

Чтобы избежать так называемого Callback-Hell, вы можете начать использовать async.js.

Async.js помогает структурировать ваши приложения и упрощает понимание потока управления.

Давайте рассмотрим короткий пример использования Async.js, а затем перепишем его с помощью промисов.

Следующий фрагмент перебирает три файла и выводит системную информацию по каждому:

Примечание переводчика: если вы пользуетесь Node.js версии 7 и выше, лучше воспользоваться встроенными конструкциями языка, такими как async/await.

Промисы

Объект Promise используется для отложенных и асинхронных вычислений. Промис представляет собой операцию, которая еще не завершена, но ожидается в будущем.

На практике предыдущий пример можно переписать следующим образом:

Конечно, если вы используете метод, возвращающий промис, то пример будет заметно компактнее.

Асинхронное и синхронное программирование

С некоторых пор я получаю много вопросов об асинхронном программировании. И, поняв, что данная тема интересует многих моих читателей, я решил написать статью, для объяснения этих терминов, тем более, что асинхронное программирование является очень важной частью современного Интернета.

Для начала необходимо отметить, что существуют две совершенно разные концепции: первая — синхронная и асинхронная модели программирования, а вторая — однопоточные и многопоточные среды. Каждая из моделей программирования (синхронная и асинхронная) может работать как в однопоточной, так и в многопоточной среде.

Модель синхронного программирования.

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

Однопоточная среда — Single Threaded — если у нас есть пара задач, которые необходимо выполнить, а текущая система предоставляет только один поток, тогда задачи назначаются потоку одна за другой. Наглядно это можно изобразить вот так:

,где Thread 1 — один поток, Task 1 и Task 2, Task 3, Task 4 – соответствующие задачи.

Мы видим, что у нас есть поток (Thread 1) и четыре задачи, которые нужно выполнить. Поток начинает работу над задачами и завершает все задачи одну за другой.

Многопоточная среда — Multi-Threaded — в этой среде мы используем несколько потоков, которые могут выполнять эти задачи одновременно. Это означает, что у нас есть пул потоков (новые потоки также могут создаваться по необходимости на основе доступных ресурсов) и множество задач.

Мы видим, что у нас есть четыре потока и столько же задач. Поэтому каждый поток выполняет одну задачу и завершает ее. Это идеальный сценарий, но в обычных условиях у нас, как правило, больше задач, чем количество доступных потоков. И поэтому, когда один поток закончит выполнять некоторую задачу, он немедленно приступит к выполнению другой. Обратите внимание также и на тот факт, что новый поток создается не каждый раз, потому что ему нужны системные ресурсы, такие как такты процессора и память, которых может оказаться недостаточно.

Теперь давайте поговорим об асинхронной модели и о том, как она ведет себя в однопоточной и многопоточной среде.

Модель асинхронного программирования.

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

мы видим, что один поток отвечает за выполнение всех задач, чередуя их, друг с другом.

Если наша система способна создавать несколько потоков, то все потоки могут работать по асинхронной модели.

Мы видим, что те же задачи T4, T5, T6 обрабатываются несколькими потоками. В этом и состоит красота и сила этого сценария. Как вы можете видеть, задача T4 была запущена первой в потоке Thread 1 и завершена в потоке Thread 2. Точно так же T6 завершается в Thread 2, Thread 3 и Thread 4.

Итак, всего у нас четыре сценария –

  • Синхронный однопоточный
  • Синхронный многопоточный
  • Асинхронный однопоточный
  • Асинхронный многопоточный

Преимущества асинхронного программирования

Для любого приложения важны две вещи: удобство использования и производительность. Удобство использования важно потому, что когда пользователь нажимает кнопку, чтобы сохранить некоторые данные, это требует выполнения нескольких небольших задач, таких как чтение и заполнение данных во внутреннем объекте, установление соединения с SQL сервером и сохранение запроса там и. т. д.

Так как SQL-сервер, например, скорее всего, работает на другом компьютере в сети и работает под другим процессом, это может занять много времени. А, если приложение работает в одном потоке, тогда экран устройства пользователя будет находиться в неактивном состоянии до тех пор, пока все задачи не будут завершены, что является примером очень плохого пользовательского интерфейса. Вот почему многие приложения и новые фреймворки полностью полагаются на асинхронную модель, так как она позволяет выполнять множество задач, при этом сохраняя отзывчивость интерфейса.

Эффективность приложения также очень важна. Подсчитано, что при выполнении запроса около 70-80% времени теряется в ожидании зависимых задач. Поэтому, это место где асинхронное программирование как нельзя лучше придется кстати.

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

Читать еще:  Программирование в экселе

Копирование материалов разрешается только с указанием автора (Михаил Русаков) и индексируемой прямой ссылкой на сайт (http://myrusakov.ru)!

Добавляйтесь ко мне в друзья ВКонтакте: http://vk.com/myrusakov.
Если Вы хотите дать оценку мне и моей работе, то напишите её в моей группе: http://vk.com/rusakovmy.

Если Вы не хотите пропустить новые материалы на сайте,
то Вы можете подписаться на обновления: Подписаться на обновления

Если у Вас остались какие-либо вопросы, либо у Вас есть желание высказаться по поводу этой статьи, то Вы можете оставить свой комментарий внизу страницы.

Порекомендуйте эту статью друзьям:

Если Вам понравился сайт, то разместите ссылку на него (у себя на сайте, на форуме, в контакте):

Она выглядит вот так:

  • BB-код ссылки для форумов (например, можете поставить её в подписи):
  • Комментарии ( 0 ):

    Для добавления комментариев надо войти в систему.
    Если Вы ещё не зарегистрированы на сайте, то сначала зарегистрируйтесь.

    Copyright © 2010-2020 Русаков Михаил Юрьевич. Все права защищены.

    Асинхронное программирование в C#. Вступление.

    8 октября 2018 г.

    Многие слышали об этом, но не многие используют его в своем коде. Между тем, никакие более-менее серьезные программы с клиент-серверной архитектурой не обойдутся без асинхронного программирования. Обмен данными с базой, взаимодействие клиента и сервера – это требует времени, которое можно занять другими процессами вместо ожидания.

    При синхронном выполнения операции, поток блокируется другим. И приходится ждать выполнения этого второго процесса, чтобы вернуть управление первому. Это вызывает не нужную трату ресурсов, ведь поток с единственной задачей может достаточно долго ожидать ответ. От базы данных, например, или веб-сервиса.

    И если временные ресурсы может сберечь многопоточность (благо современные процессоры это позволяют), то ресурсы памяти это не спасет. Ведь по сути, многопоточность – это тоже синхронное выполнение операций. Просто их несколько.

    Настоящим выходом будет использование асинхронной обработки. При таком подходе, мы можем использовать несколько потоков передающих управление то одной, то другой задаче.

    То есть, ожидая ответа от другой задачи, текущая не блокирует поток, а предоставляет его другой задаче.

    Давайте посмотрим на рисунок:

    В этом случае используется однопоточный асинхронный подход.

    А здесь многопоточный асинхронный поток.

    Каждый поток выполняет множество задач. Когда одна из задач останавливается в ожидании, берется другая. Таким образом задачи перетекают от одного потока к другому, в зависимости от того, который освободился первым. Из рисунка видно, что Task 1 начала выполняться в первом потоке, а закончила во втором.

    Рассмотрим еще один рисунок – диаграмма последовательности:

    Диаграмма описывает поведение потоков для клиент-серверного приложения. Клиент отправляет запрос данных от сервера, и, вместо того чтобы «зависнуть» ожидая ответ (как в синхронном подходе) продолжает работать, предоставляя пользователю другой функционал приложения. Так что, если вы веб разработчик, то без асинхронности – никуда.

    Давайте немного разберемся в теории. Всего можно выделить три шаблона асинхронного программирования:

    • Asynchronous Programming Model (APM);
    • Event-based Asynchronous Pattern (EAP);
    • Task-based Asynchronous Pattern(TAP).

    Asynchronous Programming Model появился еще в первой версии .Net Framework. APM позволил создавать асинхронные версии синхронных методов посредством двух методов — Begin и End .

    Итак, всего два метода:

    Метод Begin начинает асинхронную операцию. Он принимает параметры args, callback – делегат на метод, вызываемый после выполнения асинхронного метода, объект userState, который используется для передачи информации о состоянии конкретного приложения в метод, вызываемый при завершении асинхронной операции.

    Метод возвращает объект типа IAsyncResult который хранит информацию об асинхронной операции.

    Метод End завершает асинхронную операцию. Он принимает на вход объект, типа IAsyncResult, а возвращает TResult, который на самом деле вернет тип, определенный в синхронной копии этого метода.

    Давайте посмотрим, как используется этот шаблон на упрощенном примере:

    Мы вызвали метод Begin в обработчике события нажатия кнопки. В качестве параметра передаем callback в этот метод. И, уже в самом callback-е вызываем парный метод – End.

    К недостаткам асинхронной модели программирования можно отнести:

    • Необходимо создавать функцию обратного вызова;
    • Нет возможности прервать операцию.
    • Нет возможности отказаться от вызова метода обратного вызова, если он был передан при начале операции.
    • Нет оповещения о прогрессе операции или о промежуточных результатах.

    Eventbased Asynchronous Pattern

    Этот шаблон асинхронного программирования появился во второй версии фреймворка .Net. Он основан на событиях и асинхронных методах. Класс, реализующий этот шаблон будет содержать методы MethodNameAsync и MethodNameAsyncCancel (если нужна обработка отмены операции), и событие MethodNameCompleted. В этом же классе можно разместить синхронные версии методов, работающие с тем же потоком. Чаще всего этот шаблон используют при работе с веб сервисами. Например, ajax реализует Event-based Asynchronous Pattern. Получить результат асинхронной операции и обработать ошибки можно только в обработчике события MethodNameCompleted.

    Шаблон асинхронного программирования, основанный на событиях решил некоторые проблемы предшественника:

    • Объявление метода для получения результата асинхронной операции;
    • Не предусмотрен механизм оповещения о прогрессе операции.

    Однако, данный шаблон все равно имеет ряд недостатков:

    • Нет возможности передать контекст вызова операции (пользовательские данные) в метод обработки результата;
    • Не все операции можно прерывать. Методы, поддерживающие только одну операцию в очереди невозможно прервать;
    • Невозможно задать, в контексте какого потока будут вызываться методы обратного вызова.

    Task-based Asynchronous Pattern (TAP)

    Третий шаблон асинхронного программирования появился в .Net Framework 4.0. Из названия понятно, что он базируется на использовании задач. Основа TAP — два типа System.Threading.Tasks.Task и System.Threading.Tasks.Task

    TAP позволяет разработчикам определять асинхронные функции в рамках одного метода. Теперь нет необходимости создавать функции начала и конца асинхронной операции, а потом еще и обратного вызова. Это конечно облегчает труд программиста, уменьшает порог вхождения в технологию, и просто делает программирование приятным.

    TAP использует задачи для выполнения операций. Для каждой задачи используется отдельный поток, который берется из пула потоков. После выполнения задачи, поток возвращается в пул.

    Модификатор «async» — этот модификатор применяется к методу или лямбда-выражению, или анонимному методу — он указывает, что метод является асинхронным и сигнализирует о возможности одного или нескольких вхождений оператора ожидания в этот метод.

    Давайте посмотрим на пример определения метода:

    Обратите внимание на ключевые слова async и await. Именно эти операторы сигнализируют, что используется Task-based Asynchronous Pattern. Модификатор async указывает, что метод асинхронный. А оператор await может быть вызван внутри метода один или несколько раз. Он приостанавливает выполнение задачи до получения результата, в то время как поток продолжает свою работу.

    А вот пример исползования TAP из жизни. Вызов веб службы:

    А вот другой способ вызова, еще более простой и понятный:

    Этот «облегченный» вариант использования await стал доступен в .Net Framework 4.5.

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

    На текущий момент Microsoft рекомендует использовать именно этот шаблон для реализации асинхронных вызовов при разработке компонентов.

    Что же касается целесообразности использования этого шаблона. Использование TAP увеличит пропускную способность сервера. Однако, расходы на создание асинхронного процесса могут нивелировать преимущества, если у вас интенсивность обмена (например, клиент-сервера) небольшая. В этом случае синхронный подход будет работать быстрее.

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

    Ссылка на основную публикацию
    Adblock
    detector