Асинхронные обратные вызовы и генераторы Python
Я пытаюсь преобразовать синхронную библиотеку для использования внутренней асинхронной структуры ввода-вывода. У меня есть несколько методов, которые выглядят так:
def foo:
....
sync_call_1() # synchronous blocking call
....
sync_call_2() # synchronous blocking call
....
return bar
Для каждой из синхронных функций (sync_call_*
) я написал соответствующую асинхронную функцию, которая выполняет обратный вызов. Например.
def async_call_1(callback=none):
# do the I/O
callback()
Теперь для вопроса о новичке python - что проще всего перевести существующие методы для использования этих новых методов async? То есть метод foo()
выше должен быть теперь:
def async_foo(callback):
# Do the foo() stuff using async_call_*
callback()
Один очевидный выбор - передать обратный вызов в каждый асинхронный метод, который эффективно "возобновляет" вызывающую функцию "foo", а затем вызывает глобальный вызов в самом конце метода. Однако это делает код хрупким, уродливым, и мне нужно будет добавить новый обратный вызов для каждого вызова метода async_call_*
.
Есть ли простой способ сделать это с использованием идиомы python, например, генератора или сопрограммы?
Ответы
Ответ 1
UPDATE: возьмите это с солью, поскольку я не в курсе современных асинхронных разработок python, включая gevent и asyncio и на самом деле не имеют серьезного опыта работы с асинхронным кодом.
В Python существует 3 общих подхода к асинхронному кодированию без потоков:
-
Обратные вызовы - уродливые, но работоспособные, Twisted делает это хорошо.
-
Генераторы - приятные, но требуют, чтобы весь ваш код соответствовал стилю.
-
Используйте реализацию Python с реальными задачами - Stackless (RIP) и greenlet.
К сожалению, в идеале вся программа должна использовать один стиль, или вещи становятся сложными. Если вы в порядке со своей библиотекой, разоблачающей полностью синхронный интерфейс, вы, вероятно, все в порядке, но если вы хотите, чтобы несколько звонков в вашу библиотеку работали параллельно, особенно параллельно с другим асинхронным кодом, тогда вам нужно общее событие "реактор", может работать со всем кодом.
Итак, если у вас есть (или ожидаете, что у пользователя будет) другой асинхронный код в приложении, то использование этой модели, вероятно, будет разумным.
Если вы не хотите понимать весь беспорядок, подумайте о том, как использовать старые старые темы. Они также уродливы, но работают со всем остальным.
Если вы хотите понять, как сопроводители могут вам помочь, и как они могут вас усложнить, Дэвид Бэзли "Любопытный курс по Corouts и Concurrency" - это хороший материал.
Greenlets может быть актуальным самым чистым способом, если вы можете использовать расширение. У меня нет опыта с ними, поэтому не могу сказать много.
Ответ 2
Существует несколько способов для задач мультиплексирования. Мы не можем сказать, что лучше для вашего дела, не имея более глубоких знаний о том, что вы делаете. Вероятно, самым простым/универсальным способом является использование потоков. Взгляните на этот вопрос для некоторых идей.
Ответ 3
Вам нужно сделать функцию foo
также асинхронной. Как насчет этого подхода?
@make_async
def foo(somearg, callback):
# This function is now async. Expect a callback argument.
...
# change
# x = sync_call1(somearg, some_other_arg)
# to the following:
x = yield async_call1, somearg, some_other_arg
...
# same transformation again
y = yield async_call2, x
...
# change
# return bar
# to a callback call
callback(bar)
И make_async
можно определить следующим образом:
def make_async(f):
"""Decorator to convert sync function to async
using the above mentioned transformations"""
def g(*a, **kw):
async_call(f(*a, **kw))
return g
def async_call(it, value=None):
# This function is the core of async transformation.
try:
# send the current value to the iterator and
# expect function to call and args to pass to it
x = it.send(value)
except StopIteration:
return
func = x[0]
args = list(x[1:])
# define callback and append it to args
# (assuming that callback is always the last argument)
callback = lambda new_value: async_call(it, new_value)
args.append(callback)
func(*args)
ВНИМАНИЕ: я не тестировал этот