Как стать автором
Обновить

Комментарии 1

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

Так что попробую дополнить:
В целом, реализовывать асинхронный подход можно несколькими способами:
1. При отсутствии поддержки языка — мы можем организовать некоторый список задач, которые, например, неблокирующе читают содержимое сокетов, дописывают в свою структуру новые данные, парсят и при окончательном парсинге — выдают результат (например, как здесь.). В данном случае, у нас получается довольно запутанный код, так как нам приходится учитывать текущее состояние, в случае HTTP — состояние «мы ждём и парсим заголовки» или, если в заголовках было тело — «мы принимаем тело ответа».
# вызываем эту функцию много раз, пока принимаем http-данные с принятым кусочком
# состояние хранится в каком-нибудь объекте-таске
def append_data_chunk(chunk):
  if self.state = "recv_headers":
    self.data += chunk
    if "\r\n\r\n" in self.data:
      self.body, self.body_len = self.parse_headers(self.data)

    if self.body and self.body_len > 0:
      self.state = "recv_body"
  if self.state = "recv_body":
    self.body += chunk
    if len(self.body) == self.body_len:
      return self.headers, self.body
    # ...

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

2. Если есть поддержка со стороны языка — мы можем использовать корутины.
Корутина — это такая функция, которая фактически может поставить себя на паузу, выдав какой-то промежуточный результат (или приняв, или вовсе ничего не сделать и просто выдать себя назад), и её можно будет вызвать снова, с того же места где её прервали, учитывая все промежуточные переменные, области видимости и состояние функции в целом. Что позволяет писать асинхронный код так, как будто он синхронный:
# получаем генератор, и вызываем его много раз подряд, 
# отправляя новые кусочки принятых данных,
# состояние хранится прямо внутри функции
def deal_with_request():
  headerstring = ""
  # ждём, пока при очередном вызове корутино-генератора
  # не придёт хвост http-запроса
  while not "\r\n\r\n" in headerstring:
    headerstring += yield
  
  headers, bodystring = parse_headers(headerstring)
  len = headers.get("Content-Length", 0)
  # мы можем вернуть заголовки при отсутствии тела
  if len == 0:
    return headers, bodystring

  # или продолжить требовать отправлять нам кусочки
  # пока не будет принято тело сообщения
  while len(bodystring) < len:
    bodystring += yield

  return headers, bodystring

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


В чём разница между двумя подходами?
— Первый — универсален, но в нём легко запутаться и сложно поддерживать, особенно при множестве состояний. Функция выглядит как множество goto, от начала до разных точек if-блоков (технически являющихся goto-метками).
— Второй — сложнее в общем понимании процесса (необходимо разбираться с yield'ами и корутинами, писать для них обёртки) и медленнее, ибо генераторы в принципе более медленные чем функции (требуется сохранять/восстанавливать стек), но с их помощью, асинхронный код в целом выглядит прямым.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий