Примечание: этот ответ охватывает CPython и инфраструктуру asyncio.Однако эти концепции должны применяться к другим реализациям Python, а также к другим асинхронным средам.
Как мне написать C-функцию, чтобы я мог await
использовать ее?
Самый простой способ написать функцию C, результат которой можно ожидать, это вернуть ей уже созданный ожидаемый объект, такой как asyncio.Future
.Перед возвратом Future
код должен обеспечить, чтобы будущие результаты устанавливались каким-либо асинхронным механизмом.Все эти подходы, основанные на сопрограммах, предполагают, что ваша программа работает в некотором цикле событий, который знает, как планировать сопрограммы.
Но возвращать будущее не всегда достаточно - возможно, мы хотели бы определить объектс произвольным числом точек подвеса.Возврат будущего приостанавливается только один раз (если возвращенное будущее не завершено), возобновляется, когда будущее завершено, и все.Ожидаемый объект, эквивалентный async def
, который содержит более одного await
, не может быть реализован путем возврата будущего, он должен реализовать протокол, который обычно реализуют сопрограммы.Это похоже на итератор, реализующий пользовательский __next__
и используемый вместо генератора.
Определение пользовательского ожидаемого
Чтобы определить собственный ожидаемый тип, мы можем обратиться к PEP 492,which указывает , какие именно объекты могут быть переданы await
.Кроме функций Python, определенных с async def
, пользовательские типы могут делать объекты ожидаемыми, определяя специальный метод __await__
, который Python / C отображает на tp_as_async.am_await
часть структуры PyTypeObject
.
Это означает, что в Python / C вы должны сделать следующее:
- указать ненулевое значение для поля
tp_as_async
вашего типа расширения. - имеет
am_await
член, указывающий на функцию C, которая принимает экземпляр вашего типа и возвращает экземпляр другого типа расширения, который реализует протокол итератора , т.е.определяет tp_iter
(тривиально определенное как PyIter_Self
) и tp_iternext
. - , итератор
tp_iternext
должен продвигать конечный автомат сопрограммы.Каждое неисключительное возвращение из tp_iternext
соответствует приостановке, а последнее исключение StopIteration
означает окончательное возвращение из сопрограммы.Возвращаемое значение сохраняется в свойстве value
StopIteration
.
Чтобы сопрограмма была полезной, она также должна иметь возможность связываться с циклом событий, который ее возбуждает, чтобы онаможет указать, когда он должен быть возобновлен после того, как он был приостановлен.Большинство сопрограмм, определенных asyncio, ожидают, что они будут выполняться в цикле событий asyncio, и внутренне используют asyncio.get_event_loop()
(и / или принимают явный аргумент loop
) для получения своих служб.
Пример сопрограммы
Чтобы проиллюстрировать, что должен реализовывать код Python / C, давайте рассмотрим простую сопрограмму, выраженную в виде Python async def
, например, этот эквивалент asyncio.sleep()
:
async def my_sleep(n):
loop = asyncio.get_event_loop()
future = loop.create_future()
loop.call_later(n, future.set_result, None)
await future
# we get back here after the timeout has elapsed, and
# immediately return
my_sleep
создаетFuture
, организует его завершение (его результат должен быть установлен) за n секунд и приостанавливает себя до завершения будущего.В последней части используется await
, где await x
означает «разрешить x
решать, будем ли мы сейчас приостанавливать или продолжать выполнение».Неполное будущее всегда решает приостановить, и в особых случаях драйвер сопрограммы asyncio Task
дает фьючерсы на их приостановку на неопределенный срок и связывает их завершение с возобновлением задачи.Механизмы приостановки других циклов событий (curio и т. Д.) Могут различаться в деталях, но основная идея та же: await
- необязательное приостановление выполнения.
__await__()
, возвращающее генератор
Чтобы перевести это на C, нам нужно избавиться от магического определения функции async def
, а также от точки подвеса await
.Удаление async def
довольно просто: эквивалентная обычная функция просто должна вернуть объект, который реализует __await__
:
def my_sleep(n):
return _MySleep(n)
class _MySleep:
def __init__(self, n):
self.n = n
def __await__(self):
return _MySleepIter(self.n)
Метод __await__
объекта _MySleep
, возвращаемый my_sleep()
, будет автоматически вызываться оператором await
для преобразования ожидаемого объекта (всего, что передано в await
) витератор.Этот итератор будет использоваться для запроса ожидаемого объекта, выбирает ли он приостановку или предоставляет значение.Это очень похоже на то, как оператор for o in x
вызывает x.__iter__()
для преобразования итерируемого x
в конкретный итератор .
Когда возвращаемый итератор выбираетприостановить, это просто необходимо произвести значение.Значение значения, если оно есть, будет интерпретироваться драйвером сопрограммы, обычно частью цикла событий.Когда итератор решает прекратить выполнение и вернуться из await
, он должен прекратить итерацию.Используя генератор в качестве удобной реализации итератора, _MySleepIter
будет выглядеть так:
def _MySleepIter(n):
loop = asyncio.get_event_loop()
future = loop.create_future()
loop.call_later(n, future.set_result, None)
# yield from future.__await__()
for x in future.__await__():
yield x
Когда await x
отображается на yield from x.__await__()
, наш генератор должен исчерпать итератор, возвращаемый future.__await__()
.Итератор, возвращаемый Future.__await__
, выдаст, если будущее будет неполным, и вернет результат будущего (который мы здесь игнорируем, но yield from
фактически предоставляет) в противном случае.
__await__()
, который возвращает пользовательский итератор
Последним препятствием для реализации C на my_sleep
в C является использование генератора для _MySleepIter
.К счастью, любой генератор может быть преобразован в итератор с состоянием, чей __next__
выполняет кусок кода до следующего ожидания или возврата.__next__
реализует версию кода генератора для конечного автомата, где yield
выражается возвращением значения, а return
повышением StopIteration
.Например:
class _MySleepIter:
def __init__(self, n):
self.n = n
self.state = 0
def __iter__(self): # an iterator has to define __iter__
return self
def __next__(self):
if self.state == 0:
loop = asyncio.get_event_loop()
self.future = loop.create_future()
loop.call_later(self.n, self.future.set_result, None)
self.state = 1
if self.state == 1:
if not self.future.done():
return next(iter(self.future))
self.state = 2
if self.state == 2:
raise StopIteration
raise AssertionError("invalid state")
Перевод на C
Выше приведено довольно много типов, но оно работает и использует только конструкции, которые могут быть определены с помощью собственных функций Python / C.
На самом деле перевод двух классов в C довольно прост, но выходит за рамки этого ответа.