python - 在三重奏中,我怎样才能拥有与我的对象一样长的后台任务?

标签 python async-await python-trio

我正在编写一个将在其生命周期内产生任务的类。由于我使用的是 Trio ,如果没有托儿所,我就无法生成任务。我的第一个想法是在我的类(class)中有一个 self._nursery,我可以在其中生成任务。但似乎托儿所对象只能在上下文管理器中使用,因此它们总是在创建它们的同一范围内关闭。我不想从外部传入托儿所,因为它是一个实现细节,但我确实希望我的对象能够产生与对象一样长的任务(例如心跳任务)。

如何使用 Trio 编写这样一个具有长期后台任务的类?

最佳答案

很好的问题!

Trio 最奇怪、最具争议的决定之一是,它认为后台任务的存在不是实现细节,应该作为一部分公开你的 API。总的来说,我认为这是正确的决定,但它肯定有点实验性,并且有一些取舍。

Trio 为什么要这样做?根据我的经验,其他系统看起来好像可以抽象出后台任务或线程的存在,但实际上它会以各种方式泄漏:它们最终会破坏 control-C 处理,或者当你'重新尝试干净地退出程序,或者当您尝试取消主要操作时它们泄漏,或者您有顺序问题,因为您调用的函数已完成但它 promise 的工作仍在后台进行,或者后台任务因意外异常而崩溃,然后异常丢失,随之而来的是各种奇怪的问题……因此,虽然它可能会让你的 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 连接,那么他们可以在 websocket 管理的顶部打开一个nursery代码,然后里面有很多websockets。

不过,您可能想知道:您在哪里可以找到这个 @asynccontextmanager?好吧,它已包含在 3.7 的 stdlib 中,但还没有推出,所以根据您阅读本文的时间,您可能可能还没有使用它。在那之前,async_generator包为您提供 @asynccontextmanager 一直到 3.5。

关于python - 在三重奏中,我怎样才能拥有与我的对象一样长的后台任务?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48282841/

相关文章:

c# - 有没有办法使用 Task<T> 作为 future 值 T 的等待句柄?

python - 如何手动退出无限三重循环,如三重奏的教程 echo client

python - PIP 安装我的操作系统项目

python - Pandas - 根据其他 3 列中的值在列中设置值

python - 在 Django 中访问反向关系

python - 将两个数据框与 Pandas 合并后的行数不同

c# - 在 C# 函数中返回 JSON - Entity Framework Core

c# - .NET Socket ReadAsync 在写循环 Async/Await 期间被阻塞