[英]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.