[英]How to properly create and run concurrent tasks using python's asyncio module?
我正在嘗試使用 Python 3 的相對較新的asyncio
模塊正確理解和實現兩個同時運行的Task
對象。
簡而言之,asyncio 似乎旨在通過事件循環處理異步進程和並發Task
執行。 它提倡使用await
(應用於異步函數)作為等待和使用結果的無回調方式,而不會阻塞事件循環。 (期貨和回調仍然是一個可行的選擇。)
它還提供了asyncio.Task()
class,這是Future
的專門子類,旨在包裝協程。 最好使用asyncio.ensure_future()
方法調用。 asyncio 任務的預期用途是允許獨立運行的任務與同一事件循環中的其他任務“同時”運行。 我的理解是Tasks
連接到事件循環,然后自動在await
語句之間繼續驅動協程。
我喜歡能夠使用並發任務而不需要使用其中一個Executor
類的想法,但我還沒有找到關於實現的詳細說明。
這就是我目前的做法:
import asyncio
print('running async test')
async def say_boo():
i = 0
while True:
await asyncio.sleep(0)
print('...boo {0}'.format(i))
i += 1
async def say_baa():
i = 0
while True:
await asyncio.sleep(0)
print('...baa {0}'.format(i))
i += 1
# wrap in Task object
# -> automatically attaches to event loop and executes
boo = asyncio.ensure_future(say_boo())
baa = asyncio.ensure_future(say_baa())
loop = asyncio.get_event_loop()
loop.run_forever()
在嘗試同時運行兩個循環任務的情況下,我注意到除非任務具有內部await
表達式,否則它將卡在while
循環中,從而有效地阻止其他任務運行(很像普通的while
循環)。 但是,一旦任務必須(a)等待,它們似乎可以同時運行而沒有問題。
因此, await
語句似乎為事件循環提供了一個在任務之間來回切換的立足點,從而產生並發的效果。
帶有內部await
的示例 output :
running async test
...boo 0
...baa 0
...boo 1
...baa 1
...boo 2
...baa 2
示例 output沒有內部await
:
...boo 0
...boo 1
...boo 2
...boo 3
...boo 4
此實現是否通過了asyncio
中並發循環任務的“正確”示例?
唯一可行的方法是讓Task
提供阻塞點( await
表達式)以便事件循環處理多個任務,這是否正確?
是的,任何在您的事件循環中運行的協程都會阻止其他協程和任務運行,除非它
yield from
或await
調用另一個協程(如果使用 Python 3.5+)。 這是因為asyncio
是單線程的; 事件循環運行的唯一方法是沒有其他協程主動執行。 使用yield from
/ await
暫時掛起協程,讓事件循環有機會工作。
您的示例代碼很好,但在許多情況下,您可能不希望長時間運行的代碼不執行在事件循環內運行的異步 I/O。 在這些情況下,使用asyncio.loop.run_in_executor
在后台線程或進程中運行代碼通常更有意義。 如果您的任務受 CPU 限制, ProcessPoolExecutor
將是更好的選擇,如果您需要執行一些非asyncio
友好的 I/O,則將使用ThreadPoolExecutor
。
例如,您的兩個循環完全受 CPU 限制並且不共享任何狀態,因此最好的性能來自使用ProcessPoolExecutor
跨 CPU 並行運行每個循環:
import asyncio
from concurrent.futures import ProcessPoolExecutor
print('running async test')
def say_boo():
i = 0
while True:
print('...boo {0}'.format(i))
i += 1
def say_baa():
i = 0
while True:
print('...baa {0}'.format(i))
i += 1
if __name__ == "__main__":
executor = ProcessPoolExecutor(2)
loop = asyncio.get_event_loop()
boo = loop.run_in_executor(executor, say_boo)
baa = loop.run_in_executor(executor, say_baa)
loop.run_forever()
您不一定需要yield from x
的yield from x
來控制事件循環。
在您的示例中,我認為正確的方法是執行yield None
或等效的簡單yield
,而不是yield from asyncio.sleep(0.001)
的yield from asyncio.sleep(0.001)
:
import asyncio
@asyncio.coroutine
def say_boo():
i = 0
while True:
yield None
print("...boo {0}".format(i))
i += 1
@asyncio.coroutine
def say_baa():
i = 0
while True:
yield
print("...baa {0}".format(i))
i += 1
boo_task = asyncio.async(say_boo())
baa_task = asyncio.async(say_baa())
loop = asyncio.get_event_loop()
loop.run_forever()
協程只是普通的舊 Python 生成器。 在內部, asyncio
事件循環會保存這些生成器的記錄,並在永無止境的循環中對每個生成器調用gen.send()
。 無論何時yield
,對gen.send()
的調用gen.send()
完成並且循環可以繼續。 (我正在簡化它;查看https://hg.python.org/cpython/file/3.4/Lib/asyncio/tasks.py#l265以獲得實際代碼)
也就是說,如果您需要在不共享數據的情況下進行 CPU 密集型計算,我仍然會選擇run_in_executor
路線。
在 Python 3.10 中不推薦使用asyncio.ensure_future
和asyncio.get_event_loop
函數。
您可以通過asyncio.create_task
同時運行兩個協程say_boo
和say_baa
:
async def main():
boo = asyncio.create_task(say_boo())
baa = asyncio.create_task(say_baa())
await boo
await baa
asyncio.run(main())
你也可以使用asyncio.gather
async def main():
await asyncio.gather(say_boo(), say_baa())
asyncio.run(main())
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.