Является ли asyncio.run_in_executor неоднозначным? - PullRequest
0 голосов
/ 19 января 2019

У меня есть серверное приложение, и по запросу клиента я планирую какую-то работу, например

def work():
    time.sleep(5)

fut = asyncio.get_event_loop().run_in_executor(None, work)

I await fut позже, когда это запрошено явно. Мой вариант использования требует, чтобы run_in_executor немедленно отправлял функцию work, и в моем окружении она работает как положено (Ubuntu 16.04, Python 3.7.1).

Поскольку мое приложение зависит от этого поведения, я хотел убедиться, что оно вряд ли что-то изменит, поэтому я проверил несколько ресурсов:

  1. Документация выглядит довольно расплывчато. awaitable похоже, что он может применяться к методу или возвращаемому значению - хотя в тексте говорится, что он явно возвращает asyncio.Future.
  2. PEP 3156 , который определяет асинхронность - здесь ничего не говорится о том, что run_in_executor является сопрограммой.
  3. В некоторых вопросах, run_in_executor - это функция, которая возвращает ожидаемое, или сама сопрограмма, кажется, рассматривается как деталь реализации. См. 25675 и 32327 .
  4. AbstractEventLoop.run_in_executor указывается в качестве сопрограммы, но реализация в BaseEventLoop.run_in_executor является простой функцией.

1 и 2 в основном указывают на правильность текущего поведения, но 3 и 4 относятся к делу. Это кажется очень важной частью интерфейса, потому что если сама функция является сопрограммой, то она не начнет выполняться (поэтому не будет планировать работу), пока ее не ожидают.

Безопасно ли полагаться на текущее поведение? Если да, то разумно ли менять интерфейс AbstractEventLoop.run_in_executor на обычную функцию вместо сопрограммы?

1 Ответ

0 голосов
/ 19 января 2019

Мой сценарий использования требует, чтобы run_in_executor немедленно отправил рабочую функцию, и это ведет себя так, как ожидается в моей среде

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

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

Если вы хотите, чтобы задача запускалась независимо от недокументированного поведения, вы можете создать собственную функцию, эквивалентную run_in_executor. Это действительно сводится к объединению executor.submit и asyncio.wrap_future. Без излишеств это может быть так просто:

def my_run_in_executor(executor, f, *args):
    return asyncio.wrap_future(executor.submit(f, *args))

Поскольку executor.submit вызывается непосредственно в функции, эта версия гарантирует, что рабочая функция запускается без ожидания запуска цикла событий.

PEP 3156 прямо заявляет, что run_in_executor "эквивалентно wrap_future(executor.submit(callback, *args))", обеспечивая тем самым необходимую гарантию - но PEP не является официальной документацией, и окончательная реализация и спецификация часто расходятся с начальный ПКП.

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

async def run_now(f, *args):
    loop = asyncio.get_event_loop()
    started = asyncio.Event()
    def wrapped_f():
        loop.call_soon_threadsafe(started.set)
        return f(*args)
    fut = loop.run_in_executor(None, wrapped_f)
    await started.wait()
    return fut

fut = await run_now(work)
# here the worker has started, but not (necessarily) finished
result = await fut
# here the worker has finished and we have its return value

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

...