В трио, как я могу иметь фоновую задачу, которая живет столько же, сколько мой объект?

Я пишу класс, который будет порождать задачи при его жизни. Так как я использую Trio, я не могу создавать задачи без детской. Моей первой мыслью было иметь self._nursery в моем классе, в котором я могу создавать задачи. Но кажется, что дочерние объекты могут использоваться только в контекстном менеджере, поэтому они всегда закрываются в той же области, в которой они были созданы. Я не хочу передавать в детскую комнату извне, потому что это деталь реализации, но я хочу, чтобы мои объекты могли порождать задачи, которые длятся столько же, сколько и объект (например, задача сердцебиения).

Как я могу написать такой класс, который имеет долгоживущие фоновые задачи, используя Trio?

1 ответ

Решение

Отличный вопрос!

Одно из самых странных и противоречивых решений Trio заключается в том, что он придерживается позиции, согласно которой существование фоновой задачи не является деталью реализации и должно быть представлено как часть вашего API. В целом, я думаю, что это правильное решение, но оно определенно немного экспериментально и имеет некоторые компромиссы.

Почему Трио делает это? По моему опыту, другие системы создают впечатление, что вы можете абстрагироваться от присутствия фоновой задачи или потока, но в действительности это происходит утечкой разными способами: они заканчивают тем, что нарушают обработку control-C, или они вызывают проблемы, когда вы ' Вы пытаетесь выйти из программы без ошибок, или они протекают, когда вы пытаетесь отменить основную операцию, или у вас возникают проблемы с секвенированием, потому что вызванная вами функция завершена, но работа, которую она обещала выполнить, все еще выполняется в фоновом режиме или фоновая задача происходит сбой с неожиданным исключением, а затем исключение теряется и возникают всевозможные странные проблемы... так что, хотя в краткосрочной перспективе ваш API может показаться немного запутанным, в долгосрочной перспективе все будет проще, если вы сделаете это явным.

Кроме того, имейте в виду, что у всех остальных, пишущих и использующих библиотеки Trio, есть та же проблема, поэтому ваш API не будет казаться слишком странным:-).

Я не знаю, что вы пытаетесь сделать точно. Может быть, это что-то вроде соединения через веб-сокет, где вы хотите постоянно читать из сокета, чтобы отвечать на запросы heartbeat ("ping"). Один шаблон будет делать что-то вроде:

@asynccontextmanager
def open_websocket(url):
    ws = WebSocket()
    await ws._connect(url)
    try:
        async with trio.open_nursery() as nursery:
            nursery.start_soon(ws._heartbeat_task)
            yield ws
            # Cancel the heartbeat task, since we're about to close the connection
            nursery.cancel_scope.cancel()
    finally:
        await ws.aclose()

И тогда ваши пользователи могут использовать его как:

async with open_websocket("https://...") as ws:
    await ws.send("hello")
    ...

Если вы хотите стать более любопытным, другой вариант - предоставить одну версию, где ваши пользователи переходят в свой питомник, для экспертов:

class WebSocket(trio.abc.AsyncResource):
    def __init__(self, nursery, url):
        self._nursery = nursery
        self.url = url

    async def connect(self):
        # set up the connection
        ...
        # start the heartbeat task
        self._nursery.start_soon(self._heartbeat_task)

    async def aclose(self):
        # you'll need some way to shut down the heartbeat task here
        ...

а затем также предоставьте удобный API для тех, кто хочет только одно соединение и не хочет связываться с питомниками:

@asynccontextmanager
async def open_websocket(url):
    async with trio.open_nursery() as nursery:
        async with WebSocket(nursery, url) as ws:
            await ws.connect()
            yield ws

Основное преимущество подхода "передача в питомник" состоит в том, что если ваши пользователи хотят открывать множество подключений к веб-сокетам, произвольное количество подключений к веб-сокетам, то они могут открыть один питомник один раз в верхней части кода управления веб-сокетами и тогда есть много веб-сокетов внутри него.

Вы, вероятно, задаетесь вопросом, хотя: где вы найдете это @asynccontextmanager? Ну, он включен в stdlib в 3.7, но его еще нет, поэтому в зависимости от того, когда вы читаете это, вы можете его еще не использовать. До тех пор async_generator пакет дает вам @asynccontextmanager вплоть до 3,5.

Другие вопросы по тегам