web-dev-qa-db-ja.com

トリオでは、オブジェクトが存続する限り、バックグラウンドタスクを実行するにはどうすればよいですか?

私はその存続期間中にタスクを生成するクラスを書いています。 Trio を使用しているので、保育園なしではタスクを生成できません。私の最初の考えは、タスクを生成できるクラスにself._nurseryを含めることでした。ただし、ナーサリオブジェクトはコンテキストマネージャでのみ使用できるようであるため、それらは常に作成されたのと同じスコープで閉じられます。実装の詳細であるため、外部からナーサリーを渡したくありませんが、オブジェクトが存続する限り持続するタスク(ハートビートタスクなど)をオブジェクトが生成できるようにしたいです。

Trioを使用して、バックグラウンドタスクが長続きするこのようなクラスを作成するにはどうすればよいですか?

22
lilydjwg

素晴らしい質問です!

Trioの最も奇妙で物議を醸す決定の1つは、バックグラウンドタスクの存在はではなく実装の詳細であり、APIの一部として公開する必要があります。全体として、これは正しい決定だと思いますが、これは確かに少し実験的であり、いくつかのトレードオフがあります。

なぜトリオはこれを行うのですか?私の経験では、他のシステムでは、バックグラウンドタスクまたはスレッドの存在を抽象化できるように見えますが、実際には、さまざまな方法でリークします。最終的にはcontrol-C処理が機能しなくなったり、問題が発生したりします。プログラムを正常に終了しようとしたり、メイン操作をキャンセルしようとしたときにリークしたり、呼び出した関数が完了したが、約束した作業がバックグラウンドで実行されているためにシーケンスの問題が発生したり、バックグラウンドタスク予期しない例外でクラッシュすると、例外が失われ、あらゆる種類の奇妙な問題が発生します...したがって、短期的にはAPIが少し混乱しているように感じるかもしれませんが、長期的には、これを明示的にするとすべてが簡単になります。

また、Trioライブラリを作成して使用する他のすべての人が同じ問題を抱えているため、APIがあまり奇妙に感じられないことを覚えておいてください:-)。

あなたが何をしようとしているのか正確にはわかりません。おそらくそれは、ハートビート( "ping")要求に応答するためにソケットから常に読み取りを行いたいWebSocket接続のようなものです。 1つのパターンは、次のようなことを行うことです。

@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")
    ...

もっと凝ったものにしたい場合、別のオプションは、専門家のために、ユーザーが自分の保育園を通過する1つのバージョンを提供することです。

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
        ...

また、1つの接続だけが必要で、保育園をいじりたくない人のために、便利な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管理コードの先頭で1つのNurseryを1回開くことができることです。次に、その中にたくさんのWebSocketがあります。

ただし、おそらく疑問に思われるかもしれません。この@asynccontextmanagerはどこにありますか?まあ、それは3.7のstdlibに含まれていますが、それはまだ出ていないので、これを読んでいる時期によっては、かもしれませんそれを使用していない可能性がありますまだ。それまでは、 async_generator パッケージは3.5までずっと@asynccontextmanagerを提供します。

20