Примечание: этот ответ охватывает CPython и инфраструктуру asyncio. Однако концепции должны применяться к другим реализациям Python, а также к другим асинхронным платформам.
Как мне написать C-функцию, чтобы я мог await
работать с ней?
Самый простой способ написать C-функцию, результат которой можно ожидать, — вернуть уже созданный ожидаемый объект, например asyncio.Future
. Прежде чем возвращать Future
, код должен обеспечить установку будущего результата с помощью какого-либо асинхронного механизма. Все эти подходы, основанные на сопрограммах, предполагают, что ваша программа работает в некотором цикле обработки событий, который знает, как планировать сопрограммы.
Но возврата будущего не всегда достаточно — может быть, мы хотели бы определить объект с произвольным количеством точек подвеса. Возврат фьючерса приостанавливается только один раз (если возвращенное фьючерс не завершено), возобновляется после завершения фьючерса, и все. Ожидаемый объект, эквивалентный async def
, который содержит более одного await
, не может быть реализован путем возврата будущего, он должен реализовать протокол, который обычно реализуют сопрограммы. Это похоже на итератор, реализующий пользовательский __next__
, и его можно использовать вместо генератора.
Определение пользовательского ожидания
Чтобы определить наш собственный ожидаемый тип, мы можем обратиться к PEP 492, который определяет какие именно объекты можно передать await
. В отличие от функций Python, определенных с помощью async def
, пользовательские типы могут делать объекты ожидаемыми, определяя специальный метод __await__
, который Python/C сопоставляется с частью tp_as_async.am_await
структуры PyTypeObject
.
Это означает, что в Python/C вы должны сделать следующее:
- укажите значение, отличное от NULL, для поля
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__()
, который возвращает пользовательский итератор
Последним препятствием для реализации 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")
Перевод на С
Вышеупомянутый пример довольно типичен, но он работает и использует только те конструкции, которые могут быть определены с помощью собственных функций Python/C.
На самом деле перевод двух классов на C довольно прост, но выходит за рамки этого ответа.
person
user4815162342
schedule
30.06.2018