Возможно реализовать сопрограмму Python в C ++, но это требует некоторой работы.Вам нужно сделать то, что обычно делает для вас интерпретатор (на статических языках компилятор), и преобразовать вашу асинхронную функцию в конечный автомат.Рассмотрим очень простую сопрограмму:
async def coro():
x = foo()
y = await bar()
baz(x, y)
return 42
Invoking coro()
не выполняет ни один из его кодов, но создает ожидаемый объект, который можно запустить, а затем возобновить несколько раз.(Но вы обычно не видите эти операции, потому что они прозрачно выполняются циклом событий.) Ожидаемое может ответить двумя различными способами: 1) приостановить или 2), указывая, что это сделано.
Внутри сопрограмма await
реализует подвеску.Если сопрограмма была реализована с помощью генератора, то y = await bar()
будет десагарно:
# pseudo-code for y = await bar()
_bar_iter = bar().__await__()
while True:
try:
_suspend_val = next(_bar_iter)
except StopIteration as _stop:
y = _stop.value
break
yield _suspend_val
Другими словами, await
приостанавливает (возвращает) столько, сколько ожидает ожидаемый объект.Ожидаемый объект сигнализирует о том, что это сделано путем поднятия StopIteration
и контрабанды возвращаемого значения внутри его атрибута value
.Если выход в цикле звучит как yield from
, вы совершенно правы, и именно поэтому await
часто описывается в терминах из yield from
.Однако в C ++ у нас нет yield
( пока ), поэтому мы должны интегрировать вышеперечисленное в конечный автомат.
Чтобы реализовать async def
с нуля, нам нужноиметь тип, удовлетворяющий следующим ограничениям:
- мало что делает при построении - обычно он просто хранит полученные аргументы
- имеет метод
__await__
, который возвращаетитерация, которая может быть просто self
; - , имеет
__iter__
, который возвращает итератор, который снова может быть self
; - , имеет метод
__next__
, вызов которого реализуетодин шаг конечного автомата, с возвратом, означающим приостановку и поднятием StopIteration
, означающим окончание.
Вышеупомянутый конечный автомат сопрограммы в __next__
будет состоять из трех состояний:
- начальный, когда он вызывает функцию
foo()
sync - , следующее состояние, когда он продолжает ожидать сопрограмму
bar()
до тех пор, пока он приостанавливает (распространяет приостановки) для вызывающей стороны.Как только bar()
возвращает значение, мы можем немедленно перейти к вызову baz()
и возвращению значения через исключение StopIteration
. - конечное состояние, которое просто вызывает исключение, информирующее вызывающего абонента о том, что сопрограмма потрачена.
Таким образом, приведенное выше определение async def coro()
можно рассматривать как синтаксический сахар для следующего:
class coro:
def __init__(self):
self._state = 0
def __iter__(self):
return self
def __await__(self):
return self
def __next__(self):
if self._state == 0:
self._x = foo()
self._bar_iter = bar().__await__()
self._state = 1
if self._state == 1:
try:
suspend_val = next(self._bar_iter)
# propagate the suspended value to the caller
# don't change _state, we will return here for
# as long as bar() keeps suspending
return suspend_val
except StopIteration as stop:
# we got our value
y = stop.value
# since we got the value, immediately proceed to
# invoking `baz`
baz(self._x, y)
self._state = 2
# tell the caller that we're done and inform
# it of the return value
raise StopIteration(42)
# the final state only serves to disable accidental
# resumption of a finished coroutine
raise RuntimeError("cannot reuse already awaited coroutine")
Мы можем проверить, что наша "сопрограмма" работает с использованием реальной асинхронности:
>>> class coro:
... (definition from above)
...
>>> def foo():
... print('foo')
... return 20
...
>>> async def bar():
... print('bar')
... return 10
...
>>> def baz(x, y):
... print(x, y)
...
>>> asyncio.run(coro())
foo
bar
20 10
42
Оставшаяся часть - написать класс coro
в Python / C или в pybind11.