Python - как реализовать C-функцию как ожидаемую (сопрограмма) - PullRequest
0 голосов
/ 25 июня 2018

Среда: совместная ОСРВ на С и виртуальная машина с микропифонами - одна из задач.

Чтобы виртуальная машина не блокировала другие задачи ОСРВ, я вставляю RTOS_sleep() в vm.c:DISPATCH() так что после выполнения каждого байт-кода виртуальная машина передает управление следующей задаче RTOS.

Я создал интерфейс uPy для асинхронного получения данных с физической шины данных - может быть CAN, SPI, ethernet - с использованием производителя-потребительский шаблон проектирования.

Использование в uPy:

can_q = CANbus.queue()
message = can_q.get()

Реализация в C такова, что can_q.get() НЕ блокирует ОСРВ: она опрашивает C-очередь и, если сообщениене получен, он вызывает RTOS_sleep(), чтобы дать другому заданию возможность заполнить очередь.Все синхронизировано, потому что очередь C обновляется только другой задачей RTOS, а задачи RTOS переключаются только тогда, когда вызывается RTOS_sleep(), то есть кооператив

Реализация C в основном:

// gives chance for c-queue to be filled by other RTOS task
while(c_queue_empty() == true) RTOS_sleep(); 
return c_queue_get_message();

Хотя оператор Python can_q.get() не блокирует ОСРВ, он блокирует скрипт uPy.Я хотел бы переписать его, чтобы я мог использовать его с async def то есть сопрограммой и не блокировать скрипт uPy.

Не уверен в синтаксис но что-то вроде этого:

can_q = CANbus.queue()
message = await can_q.get()

ВОПРОС

Как мне написать C-функцию, чтобы я мог await на ней?

Я бы предпочел ответ на CPython и micropython, но я бы принял ответ только на CPython.

1 Ответ

0 голосов
/ 30 июня 2018

Примечание: этот ответ охватывает 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 довольно прост, но выходит за рамки этого ответа.

...