Модуль asyncio Python - Блог ITVDN
ITVDN: курсы программирования
Видеокурсы по
программированию

Выбери свою IT специальность

Начать бесплатно

Выбери свою IT специальность

Начать бесплатно

Модуль asyncio Python

Python live online День программиста на ITVDN. Скидки на обучение до 50%

Введение

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


В Python 3.4 был включён модуль asyncio, который, на самом деле, был доступен в виде отдельного пакета на PyPI ещё для Python 3.3. Этот модуль предоставляет всю необходимую инфраструктуру для написания однопоточного конкурентного кода с использованием сопрограмм неблокирующего ввода-вывода, мультиплексирования ввода-вывода через сокеты и другие ресурсы, запуска сетевых клиентов и серверов и т.д.

Его возможности:

  • цикл событий с разными его реализациями, оптимизированными для различных операционных систем;
  • абстракции «транспорта» и «протокола» (подобные тем, что используются в фреймворке Twisted);
  • поддержка TCP, UDP, SSL, конвейеров UNIX-процессов, отложенных вызовов;
  • адаптированный для использования с циклом событий класс Future, который представляет ещё не вычисленный результат асинхронной функции;
  • сопрограммы и задачи, основанные на основе генераторов;
  • поддержка отмены Future и сопрограмм;
  • примитивы синхронизации для использования с сопрограммами;
  • возможность запуска задач в пуле потоков либо пуле процессов, что позволяет даже взаимодействовать с библиотеками, которые совершают блокирующий ввод-вывод.

В основе модуля asyncio лежит цикл событий (event loop). Он отвечает за:

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

Сопрограммы в asyncio – это генераторы, которые отвечают определённым требованиям. Ко всем сопрограммам должен быть применён декоратор @asyncio.coroutine.

Действия, которые поддерживают сопрограммы asyncio:

  • result = yield from future – приостановка выполнения сопрограммы до получения future значения и присвоение этого значения переменной result (если future отменён, возникает исключение CancelledError);
  • result = yield from coroutine – ожидание завершения работы другой сопрограммы и получение её результата;
  • return result – возврат значения в сопрограмму, ожидающую данную;
  • raise exception – выброс исключения для обработки его ожидающей сопрограммой (если оно не обработано в текущей).

Рассмотрим пример двух сопрограмм, одна из которых вызывает другую, производящую какие-то затратные вычисления (на самом деле, для простоты примера она будет просто приостанавливать своё выполнение на одну секунду и возвращать квадрат числа).

import asyncio

@asyncio.coroutine

def time_consuming_computation(x):

    print('Computing {0} ** 2...'.format(x))

    yield from asyncio.sleep(1)

    return x ** 2

@asyncio.coroutine

def process_data(x):

    result = yield from time_consuming_computation(x)

    print('{0} ** 2 = {1}'.format(x, result))

if __name__ == '__main__':

    loop = asyncio.get_event_loop()

    loop.run_until_complete(process_data(238))

    loop.close()

Функция get_event_loop модуля asyncio возвращает объект цикла событий, и мы используем его метод run_until_complete для запуска сопрограммы.

Какое же в данном случае преимущество перед обыкновенными функциями? Во время работы сопрограммы asyncio.sleep выполнение программы не блокируется, и, если бы у нас были другие запланированные для выполнения задачи, они могли бы в это время выполняться в том же самом потоке.

Данные сопрограмм

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

Можем переписать созданный ранее пример при помощи модуля asyncio.

import asyncio

import random

import time

@asyncio.coroutine

def consume():

    """Сопрограмма обработки данных"""

    running_sum = 0

    count = 0

    while True:

        data = yield from produce()

        running_sum += data

        count += 1

        print('Got data: {}\nTotal count: {}\nAverage: {}\n'.format(

            data, count, running_sum / count))

@asyncio.coroutine

def produce():

    """Сопрограмма выдачи данных."""

    yield from asyncio.sleep(0.5)

    data = random.randint(0, 100)

    return data

def main():

    loop = asyncio.get_event_loop()

    loop.run_until_complete(consume())

    loop.close()

if __name__ == '__main__':

    main()

 

Обратите внимание, что пришлось изменить логику его работы: теперь основной является сопрограмма consumer, а producer выдаёт одну порцию данных. Причиной этого является рассмотренные ранее ограничения, накладываемые на сопрограммы asyncio, которые логично следуют из основного предназначения данного модуля: совершение асинхронного ввода-вывода. В более реальном примере аналог сопрограммы producer мог бы, например, получать данные с внешнего сервера или базы данных.

КОММЕНТАРИИ И ОБСУЖДЕНИЯ
Python live online День программиста на ITVDN. Скидки на обучение до 50%

Пакеты подписки с доступом ко всем курсам и сервисам

Стартовый
  • Все видеокурсы на 3 месяца
  • Тестирование по 10 курсам
  • Проверка 5 домашних заданий
  • Консультация с тренером 30 мин
Базовый
  • Все видеокурсы на 6 месяцев
  • Тестирование по 16 курсам
  • Проверка 10 домашних заданий
  • Консультация с тренером 60 мин
Премиум
  • Все видеокурсы на 12 месяцев
  • Тестирование по 24 курсам
  • Проверка 20 домашних заданий
  • Консультация с тренером 120 мин
Notification success