Рубрики
Без рубрики

Совместная многозадачность с сопрограммами

Автор оригинала: Doug Hellmann.

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

Запуск сопрограммы

Есть несколько разных способов запустить сопрограмму из цикла обработки событий asyncio . Самый простой – использовать run_until_complete () , передавая ему сопрограмму напрямую.

asyncio_coroutine.py

import asyncio


async def coroutine():
    print('in coroutine')


event_loop  asyncio.get_event_loop()
try:
    print('starting coroutine')
    coro  coroutine()
    print('entering event loop')
    event_loop.run_until_complete(coro)
finally:
    print('closing event loop')
    event_loop.close()

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

$ python3 asyncio_coroutine.py

starting coroutine
entering event loop
in coroutine
closing event loop

Возвращение значений из сопрограмм

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

asyncio_coroutine_return.py

import asyncio


async def coroutine():
    print('in coroutine')
    return 'result'


event_loop  asyncio.get_event_loop()
try:
    return_value  event_loop.run_until_complete(
        coroutine()
    )
    print('it returned: {!r}'.format(return_value))
finally:
    event_loop.close()

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

$ python3 asyncio_coroutine_return.py

in coroutine
it returned: 'result'

Цепочка сопрограмм

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

asyncio_coroutine_chain.py

import asyncio


async def outer():
    print('in outer')
    print('waiting for result1')
    result1  await phase1()
    print('waiting for result2')
    result2  await phase2(result1)
    return (result1, result2)


async def phase1():
    print('in phase1')
    return 'result1'


async def phase2(arg):
    print('in phase2')
    return 'result2 derived from {}'.format(arg)


event_loop  asyncio.get_event_loop()
try:
    return_value  event_loop.run_until_complete(outer())
    print('return value: {!r}'.format(return_value))
finally:
    event_loop.close()

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

$ python3 asyncio_coroutine_chain.py

in outer
waiting for result1
in phase1
waiting for result2
in phase2
return value: ('result1', 'result2 derived from result1')

Генераторы вместо сопрограмм

Функции сопрограмм являются ключевым компонентом дизайна asyncio . Они предоставляют языковую конструкцию для остановки выполнения части программы, сохранения состояния этого вызова и повторного входа в состояние в более позднее время, которые являются важными возможностями для инфраструктуры параллелизма.

Python 3.5 представил новые языковые функции для определения таких сопрограмм изначально с использованием async def и для передачи управления с помощью await , а примеры для asyncio используют преимущества новая функция. Более ранние версии Python 3 могут использовать функции генератора, заключенные в декоратор asyncio.coroutine () и yield from для достижения того же эффекта.

asyncio_generator.py

import asyncio


@asyncio.coroutine
def outer():
    print('in outer')
    print('waiting for result1')
    result1  yield from phase1()
    print('waiting for result2')
    result2  yield from phase2(result1)
    return (result1, result2)


@asyncio.coroutine
def phase1():
    print('in phase1')
    return 'result1'


@asyncio.coroutine
def phase2(arg):
    print('in phase2')
    return 'result2 derived from {}'.format(arg)


event_loop  asyncio.get_event_loop()
try:
    return_value  event_loop.run_until_complete(outer())
    print('return value: {!r}'.format(return_value))
finally:
    event_loop.close()

В предыдущем примере воспроизводится asyncio_coroutine_chain.py с использованием функций генератора вместо собственных сопрограмм.

$ python3 asyncio_generator.py

in outer
waiting for result1
in phase1
waiting for result2
in phase2
return value: ('result1', 'result2 derived from result1')