Как разделить длинную сопрограмму без использования await?

1

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

async def handle_message(self, message):
    message_type = message.get('type')

    if message_type == 'broadcast':
        ...
        for n in self._neighbors:
            await self.send_message(n, message)

    elif message_type == 'graph':
        ...

Я хотел бы извлечь часть, которая обрабатывает широковещательные сообщения в частный метод следующим образом:

async def handle_message(self, message):
    message_type = message.get('type')
    ...

    if message_type = 'broadcast':
        await self._handle_broadcast(message)
    elif message_type = 'graph':
        ...

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

Каким образом гарантировать, что сопрограмма выполняется немедленно и не задерживается?

Теги:
python-3.x
python-asyncio

1 ответ

2
Лучший ответ

Короче: разделите coroutine точно так же, как вы начали, используя await.

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

К лучшему или худшему, эта предпосылка ложна. При получении сопрограммы, await сразу же начинает выполнение ее без промежуточной задержки. Это только, если эта сопрограмма вызывает что-то, что заставляет ее приостанавливать (например, asyncio.sleep или чтение в сети, которое еще не имеет данных), что ваша сопрограмма приостанавливается вместе с ней - это именно то, что вы получили бы, если бы код остался в очереди.

В этом смысле await <some coroutine> работает как эквивалент coroutine регулярного вызова функции, позволяя точно использовать не-семантически изменяющийся рефакторинг, который вам нужен. Это можно продемонстрировать на примере:

import asyncio

async def heartbeat():
    while True:
        print('tick')
        await asyncio.sleep(1)

async def noop():
    pass

async def coro():
    # despite "await", this blocks the event loop!
    while True:
        await noop()

loop = asyncio.get_event_loop()
loop.create_task(heartbeat())
loop.create_task(coro())
loop.run_forever()

Вышеупомянутый код блокирует цикл события - даже если coro ничего не делает, кроме await в цикле. Поэтому await не является гарантией уступки циклу событий, сопрограмма должна делать это с помощью других средств. (Это поведение также может быть источником ошибок.)

В приведенном выше случае можно получить цикл событий " await asyncio.sleep(0) ", вставив await asyncio.sleep(0). Но такого рода вещи никогда не должны понадобиться в производственном коде asyncio, где программа должна быть структурирована так, чтобы каждая сопрограмма выполняла сравнительно небольшую работу, а затем использует await для получения большего количества данных.

  • 1
    Вау, у меня было недопонимание того, как работает await . Это и ссылка, которую вы предоставили, на самом деле многое объясняют о проблемах, с которыми я asyncio.open_connection в этом же проекте ранее, который также использует asyncio.open_connection . Спасибо!
  • 1
    @JacqueGoupil Чтобы лучше понять, как работают await и цикл обработки событий, вы также можете посмотреть это видео . Это объясняет вещи с точки зрения генераторов, с которыми большинство программистов на Python уже знакомы, но это также происходит именно так, как await работает за кулисами.
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню