В трио, как я могу иметь фоновую задачу, которая живет столько же, сколько мой объект?
Я пишу класс, который будет порождать задачи при его жизни. Так как я использую 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.