簡體   English   中英

Python3 異步事件循環和任務取消

[英]Python3 asyncio event loops and task cancelling

我一直在嘗試理解asyncio模塊以實現服務器。 我在看這個問題,它似乎問了一個類似的問題,如果不是同一個問題,但我仍在努力掌握幕后發生的工作流程。

我有以下簡單的程序,它有兩個協程,一個從終端讀取並將其放入隊列,另一個協程等待隊列中的項目並將它們簡單地打印回屏幕。

import asyncio

q = asyncio.Queue()

async def put():
    while True:
        await q.put(input())      #Input would be normaly something like client.recv()
        await asyncio.sleep(1)    #This is neccessarry but I dont understand why

async def get():
    while True:
        print(await q.get())

def run():
    loop = asyncio.get_event_loop()
    task1 = loop.create_task(put())
    task2 = loop.create_task(get())
    loop.run_forever()

run()

該程序按預期工作,但是當從put方法中刪除await asyncio.sleep(1)語句時,它停止工作。 我假設是因為循環不斷消耗線程並且消息沒有被推送。 我不明白為什么,因為我認為input()將是一個阻塞 function,因此協程應該暫停,直到 tty 上有新行可用。


第二個問題是,如果我在run()調用中使用asyncio.get_event_loop() ,解釋器會警告我沒有活動循環,但是,如文檔中所述,此調用已棄用,因此我嘗試用asyncio.new_event_loop()替換它asyncio.new_event_loop() 該程序仍然工作相同,但是我在 KeyboardInterrupt 上得到了回溯(調用asyncio.get_event_loop()不會發生)

Task was destroyed but it is pending!                                                         
task: <Task pending name='Task-1' coro=<put() running at test.py:10> wait_for=<Future pending cb=[Task.task_wakeup()]>>             
Task was destroyed but it is pending!                                                       
task: <Task pending name='Task-2' coro=<get() running at test.py:15> wait_for=<Future pending cb=[Task.task_wakeup()]>>               
Exception ignored in: <coroutine object get at 0x7f32f51369d0>                              
Traceback (most recent call last):                                                          
  File "test.py", line 15, in get     
  File "/usr/lib64/python3.10/asyncio/queues.py", line 161, in get                     
  File "/usr/lib64/python3.10/asyncio/base_events.py", line 745, in call_soon          
  File "/usr/lib64/python3.10/asyncio/base_events.py", line 510, in _check_closed     
RuntimeError: Event loop is closed                                 

我嘗試的第三個變體是使 run 方法本身異步並通過asyncio.run(run())調用調用它。

import asyncio

q = asyncio.Queue()

async def put():
    while True:
        await q.put(input())
        await asyncio.sleep(1)


async def get():
    while True:
        print(await q.get())


async def run():
    loop = asyncio.get_event_loop()
    task1 = loop.create_task(put())
    task2 = loop.create_task(get())
    await task1

asyncio.run(run())

這也很好用,但是如果我用await task2替換await task1 ,當我中斷程序時我會再次遇到錯誤。 為什么這些之間的執行不同,最后應該如何完成?

如評論中所述,使用StreamReader (您的原始代碼)問題 #1 將完美無缺地工作並且不會引起任何問題。 input()不給 aio 切換協程的機會,如果StreamReader不斷有數據,您可以嘗試將隊列限制在一定長度。


對於問題 #2,在清理期間,Python 為當前線程使用分配的循環。 在底層, asyncio.run()asyncio.get_event_loop()將循環分配給主線程。 當它找不到循環時,一切都會崩潰。

如果你想自己分配它,你可以這樣做:

def run():
    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)
    task1 = loop.create_task(put())
    task2 = loop.create_task(get())
    loop.run_forever()

請記住,您仍然缺少一些手動清理(即shutdown_asyncgensshutdown_executor ),但這是針對不同主題的。 總的來說,使用asyncio.run()通常是正確的選擇。


我無法重現問題 #3, await task1await task2都可以完美地工作。

暫無
暫無

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

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