在三重奏中,我如何才能拥有与我的对象一样长的后台任务?
In trio, how can I have a background task that lives as long as my object does?
我正在编写一个 class,它将在其生命周期内生成任务。因为我使用的是 Trio,所以我无法在没有 nursery 的情况下生成任务。我的第一个想法是在我的 class 中有一个 self._nursery
,我可以将任务生成到其中。但似乎 nursery 对象只能在上下文管理器中使用,因此它们总是在创建它们的同一范围内关闭。我不想从外部传递一个托儿所,因为它是一个实现细节,但我确实希望我的对象能够生成与对象持续时间一样长的任务(例如心跳任务)。
如何使用 Trio 编写这样一个 class,它具有长期存在的后台任务?
问得好!
Trio 最古怪、最具争议的决定之一是它认为后台任务的存在不是实现细节,应该 作为您 API 的一部分被曝光。总的来说,我认为这是正确的决定,但它确实有点实验性,需要权衡取舍。
Trio 为什么要这样做?以我的经验,其他系统让你似乎可以抽象出后台任务或线程的存在,但实际上它以各种方式泄漏:它们最终会破坏 control-C 处理,或者当你'尝试干净地退出程序,或者当您尝试取消主操作时它们会泄漏,或者您有顺序问题,因为您调用的函数已完成但它承诺要做的工作仍在后台进行,或者后台任务因意外异常而崩溃,然后异常丢失,随之而来的是各种奇怪的问题......所以虽然它可能会让你的 API 在短期内感觉有点混乱,但从长远来看,如果你明确这一点。
此外,请记住,编写和使用 Trio 库的其他人都有同样的问题,因此您的 API 不会感觉太奇怪 :-)。
我不知道你到底想做什么。也许它类似于 websocket 连接,您希望不断从套接字读取数据以响应心跳 ("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
pass-in-a-nursery 方法的主要优点是,如果您的用户想要打开大量的 websocket 连接,任意数量的 websocket 连接,那么他们可以在他们的顶部打开一个 nursery 一次websocket管理代码,然后里面有很多websockets
不过您可能想知道:您在哪里找到这个 @asynccontextmanager
?好吧,它包含在 3.7 的标准库中,但它甚至还没有出来,所以根据您阅读本文的时间,您 可能 还没有使用它。在那之前,async_generator
包让你 @asynccontextmanager
一直回到 3.5。
我正在编写一个 class,它将在其生命周期内生成任务。因为我使用的是 Trio,所以我无法在没有 nursery 的情况下生成任务。我的第一个想法是在我的 class 中有一个 self._nursery
,我可以将任务生成到其中。但似乎 nursery 对象只能在上下文管理器中使用,因此它们总是在创建它们的同一范围内关闭。我不想从外部传递一个托儿所,因为它是一个实现细节,但我确实希望我的对象能够生成与对象持续时间一样长的任务(例如心跳任务)。
如何使用 Trio 编写这样一个 class,它具有长期存在的后台任务?
问得好!
Trio 最古怪、最具争议的决定之一是它认为后台任务的存在不是实现细节,应该 作为您 API 的一部分被曝光。总的来说,我认为这是正确的决定,但它确实有点实验性,需要权衡取舍。
Trio 为什么要这样做?以我的经验,其他系统让你似乎可以抽象出后台任务或线程的存在,但实际上它以各种方式泄漏:它们最终会破坏 control-C 处理,或者当你'尝试干净地退出程序,或者当您尝试取消主操作时它们会泄漏,或者您有顺序问题,因为您调用的函数已完成但它承诺要做的工作仍在后台进行,或者后台任务因意外异常而崩溃,然后异常丢失,随之而来的是各种奇怪的问题......所以虽然它可能会让你的 API 在短期内感觉有点混乱,但从长远来看,如果你明确这一点。
此外,请记住,编写和使用 Trio 库的其他人都有同样的问题,因此您的 API 不会感觉太奇怪 :-)。
我不知道你到底想做什么。也许它类似于 websocket 连接,您希望不断从套接字读取数据以响应心跳 ("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
pass-in-a-nursery 方法的主要优点是,如果您的用户想要打开大量的 websocket 连接,任意数量的 websocket 连接,那么他们可以在他们的顶部打开一个 nursery 一次websocket管理代码,然后里面有很多websockets
不过您可能想知道:您在哪里找到这个 @asynccontextmanager
?好吧,它包含在 3.7 的标准库中,但它甚至还没有出来,所以根据您阅读本文的时间,您 可能 还没有使用它。在那之前,async_generator
包让你 @asynccontextmanager
一直回到 3.5。