簡體   English   中英

在非協程方法中調度協程

[英]Scheduling a coroutine inside a non-coroutine method

我正在維護一個項目,我需要在已經在事件循環中運行的同步 function 中安排協程。

我的問題歸結為:

import asyncio

class SomeScheduler:
    def __init__(self):
        self.workers = []

    # This is not a coroutine yet
    def close(self):
        for worker in self.workers:
            worker.close()

    def register_worker(self, worker):
        self.workers.append(worker)

    async def run(self):
        for _ in range(3):
            print("Doing stuff")
            coros = map(lambda x: x.work(), self.workers)
            await asyncio.gather(*coros)
            await asyncio.sleep(1)


class SomeWorkerA:
    def close(self):
        print("Closing WorkerA")

    async def work(self):
        print("Working WorkerA")
        await asyncio.sleep(0.2)
        print("Done WorkerA")

class SomeWorkerB:
    def close(self):
        print("Closing WorkerB")

    async def work(self):
        print("Working WorkerB")
        await asyncio.sleep(0.4)
        print("Done WorkerB")

async def main():
    sched = SomeScheduler()
    sched.register_worker(SomeWorkerA())
    sched.register_worker(SomeWorkerB())

    try:
        await sched.run()
    finally:
        sched.close()
        print("Bye")


asyncio.run(main())

由於歷史原因, SomeScheduler.close()不是協程,我無法更改 API(沒有在團隊中進行大量討論)。

現在我有了一種新型工人:

class SomeWorkerC:
    async def close(self):
        print("Closing WorkerC")
        await asyncio.sleep(10)
        print("Done closing WorkerC")

    async def work(self):
        print("Working WorkerC")
        await asyncio.sleep(0.4)
        print("Done WorkerC")

如果我在main() function 中添加sched.register_worker(SomeWorkerC()) ,那么問題是SomeWorkerC.close()沒有得到執行,我收到以下錯誤消息:

RuntimeWarning: coroutine 'SomeWorkerC.close' was never awaited
  co = None

這是有道理的,所以我想像這樣更改SomeScheduler.close()

class SomeScheduler:
    def __init__(self):
        self.workers = []

    def close(self):
        for worker in self.workers:
            co = worker.close()
            if isinstance(co, types.CoroutineType):
                loop = asyncio.get_running_loop()
                loop.create_task(co)

僅部分正確, stdout output 是:

...
Done WorkerA
Done WorkerB
Done WorkerC
Closing WorkerA
Closing WorkerB
Bye
Closing WorkerC

但我也希望print("Done closing WorkerC")被執行,但是從stdout output 中可以看出,情況並非如此。 loop.create_task()的文檔說:

調度協程的執行。 返回一個任務 object。

所以我假設整個任務都會被執行,但似乎只有在第一次await被執行之前,任務的 rest 才被執行。 我不能做loop.run_forever()loop.run_until_complete(co)因為循環仍在運行。

我什至嘗試用另一個循環替換循環:

class SomeScheduler:
    def __init__(self):
        self.workers = []

    def close(self):
        for worker in self.workers:
            co = worker.close()

            if isinstance(co, types.CoroutineType):
                current_loop = asyncio.get_running_loop()
                loop = asyncio.new_event_loop()
                asyncio.set_event_loop(loop)
                asyncio.run(co)

但這會因RuntimeError: asyncio.run() cannot be called from a running event loop ,如果我改為執行asyncio.gather(co) ,則第一部分會被執行,但隨后會引發此問題:

...
Closing WorkerA
Closing WorkerB
Bye
Closing WorkerC
_GatheringFuture exception was never retrieved
future: <_GatheringFuture finished exception=CancelledError()>
concurrent.futures._base.CancelledError

在這一點上,我什至不確定是否可以在不更改 API 並使SomeScheduler.close()成為協程的情況下解決這個問題。 我該如何解決這個問題?

您不能這樣做,因為 worker 的.close協程中的異步代碼永遠不會在正在運行的事件循環中運行,因為它當前被SomeScheduler的同步.close阻塞; 即異步工作者在調度程序的.close停止阻塞事件循環之前不會關閉,這顯然是不可能的,因為工作者的關閉是調度程序關閉的下游。

解決這個問題的方法是在調度程序的.close上游調用異步關閉; 例如

def shutdown():
    await scheduler.async_close()  # close async workers
    scheduler.close()  # close sync workers

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM