本文介紹asyncio的基本用法,asyncio通過event loop機制瘋狂調(diào)度和執(zhí)行各個coroutine
關(guān)鍵字-async/await
async def
用于定義一個coroutine. 一個coroutine需要被壓入一個事件循環(huán)才能被執(zhí)行,方式有兩種:the code inside the coroutine function won't run until you await on the function, or run it as a task to a loop-
await
用于等待一個awaitable對象,例如future實例和coroutine實例。更多future vs coroutine的介紹見于下文協(xié)程中使用await可以讓出cpu,讓event loop可以去執(zhí)行其他協(xié)程
當(dāng)一個線程中的 asyncio 事件循環(huán)遇到
await
表達(dá)式時,線程的事件循環(huán)機制會暫停當(dāng)前協(xié)程的執(zhí)行,并嘗試運行其他就緒的協(xié)程。在這個過程中,線程仍然處于運行狀態(tài)。然而,由于事件循環(huán)可以在協(xié)程之間切換,這使得線程能夠在等待 I/O 或其他異步操作期間執(zhí)行其他任務(wù)
基本概念-future vs coroutine
- 相同點:兩者都是awaitable對象
- 不同點:
實現(xiàn)方式上存在根本差異-
Future 是一個表示未來結(jié)果的對象,它通常由一個
asyncio
任務(wù)(通過asyncio.create_task
或者loop.create_task
創(chuàng)建)或其他低級別的異步操作創(chuàng)建,且通常用于封裝底層的異步操作如網(wǎng)絡(luò)請求、文件 I/O 等下面給出一個關(guān)于future創(chuàng)建和使用的基本示例
import asyncio async def main(): # return the loop instance specific to the current thread loop = asyncio.get_event_loop() # create an default future future = loop.create_future() async def set_future_result(): await asyncio.sleep(1) future.set_result("Hello, world!") print(f"future done: {future.done()}") # use another coroutine to set future loop.create_task(set_future_result()) try: print("coro main waiting") result = await future print("coro main wait done") print(f"result is {result}") except Exception as e: print(f"An error occurred: {e}") loop = asyncio.new_event_loop() loop.run_until_complete(main()) loop.close()
程序輸出:
coro main waiting future done: True coro main wait done result is Hello, world!
asyncio.sleep
這個coroutine的實現(xiàn)邏輯正是運用了類似上面的future創(chuàng)建和使用邏輯async def sleep(delay, result=None, *, loop=None): """Coroutine that completes after a given time (in seconds).""" if loop is not None: warnings.warn("The loop argument is deprecated since Python 3.8, " "and scheduled for removal in Python 3.10.", DeprecationWarning, stacklevel=2) if delay <= 0: await __sleep0() return result if loop is None: loop = events.get_running_loop() future = loop.create_future() h = loop.call_later(delay, futures._set_result_unless_cancelled, future, result) try: return await future finally: h.cancel()
-
coroutine 是一個使用
async def
定義的特殊函數(shù)- 它的內(nèi)部可以使用(但不強制使用)
await
關(guān)鍵字等待其他 coroutine 或Future
對象的返回值 - 一個coroutine必須 1. 通過
loop.create_task
提交到loop實例的任務(wù)隊列,2. 或者被其他coroutine await以加入loop實例的任務(wù)隊列。該coroutine才能夠被loop調(diào)度運行
注意,coroutine內(nèi)部邏輯不可以【同步阻塞】當(dāng)前線程,否則整個事件循環(huán)將被阻塞
下面給出兩個sleep功能的coroutine實現(xiàn),包括阻塞式實現(xiàn)和非阻塞式實現(xiàn)
import asyncio import time async def blocking_sleep(): """ blocking_sleep 調(diào)用 time.sleep(1) 阻塞當(dāng)前線程 當(dāng)前線程將被操作系統(tǒng)掛起1s,該線程上運行的事件循環(huán)自然隨之被阻塞 """ time.sleep(1) async def non_blocking_sleep(): """ non_blocking_sleep 不會阻塞當(dāng)前線程 因為asyncio.sleep(1)本身是一個coroutine,其底層邏輯是讓 thread-specific loop 執(zhí)行一個延時協(xié)程。整個過程涉及多次await主動讓出cpu,從而避免阻塞該線程上運行的事件循環(huán) """ await asyncio.sleep(1)
- 它的內(nèi)部可以使用(但不強制使用)
-
loop.run_until_complete vs loop.run_forever
它們都是啟動一個event loop的入口方法
-
run_until_complete(future)
:這個方法用于運行事件循環(huán),直到給定的協(xié)程或 asyncio.Future 對象完成。當(dāng)協(xié)程完成時,run_until_complete 將返回協(xié)程的結(jié)果(如果有),并停止事件循環(huán)。這是在事件循環(huán)中執(zhí)行單個協(xié)程的主要方法
-
run_forever()
這個方法用于運行事件循環(huán),直到顯式停止。它不接受協(xié)程或 Future 對象作為參數(shù)。當(dāng)你調(diào)用 run_forever() 時,事件循環(huán)將開始一直運行,直到你顯式調(diào)用 loop.stop() 方法。這個方法通常用于長時間運行的應(yīng)用程序,例如網(wǎng)絡(luò)服務(wù)器或后臺任務(wù)處理器。
為了在 run_forever() 模式下執(zhí)行協(xié)程,你需要使用 asyncio.create_task() 或 loop.create_task() 在事件循環(huán)中創(chuàng)建任務(wù)。這些任務(wù)將在事件循環(huán)運行時被調(diào)度執(zhí)行。
其他注意事項
create_task不會啟動事件循環(huán)
loop.create_task()
asyncio.create_task
都是用于將一個 coroutine(協(xié)程)包裝為一個 Task
對象并將其加入到事件循環(huán)中。Task
對象是 asyncio.Future
的子類
方法本身不會啟動事件循環(huán),它只是將協(xié)程加入到事件循環(huán)的待執(zhí)行隊列中。要啟動事件循環(huán)并運行協(xié)程,你需要使用如 loop.run_until_complete()
或 loop.run_forever()
等方法
loop.run_in_executor vs async.to_thread
兩者都會啟動另一個獨立線程去完成一些阻塞邏輯,區(qū)別在于:
-
執(zhí)行時間
-
loop.run_in_executor
即時起了一個線程,直接開始執(zhí)行,返回一個future,這個future按需await -
async.to_thread
將新線程包裝成coroutine并返回,必須await或者loop run才能被調(diào)度執(zhí)行
-
-
是否依賴loop
- 獨立線程與事件循環(huán)所在線程相互獨立互不影響,因此
loop.run_in_executor()
會馬上在executor中執(zhí)行線程邏輯,而不依賴loop的啟動 -
async.to_thread
將新線程包裝成coroutine,必須在loop啟動后才能被調(diào)度執(zhí)行
- 獨立線程與事件循環(huán)所在線程相互獨立互不影響,因此
loop的線程安全問題
在 Python 的 asyncio 庫中,事件循環(huán)不是線程安全的,這意味著在多線程環(huán)境中使用事件循環(huán)可能會導(dǎo)致未定義的行為或錯誤。以下是一些與事件循環(huán)線程安全相關(guān)的問題和注意事項:
不要多線程共享同一個事件循環(huán)實例。每個線程應(yīng)該有自己的事件循環(huán)實例
-
不要在一個線程中操作另一個線程的事件循環(huán)實例。例如,不要在一個線程中調(diào)用另一個線程的事件循環(huán)的
create_task()
、run_until_complete()
等方法。這可能會導(dǎo)致競態(tài)條件或其他線程安全問題如果你確實需要在一個線程中與另一個線程的事件循環(huán)交互,可以使用
asyncio.run_coroutine_threadsafe()
函數(shù)。這個函數(shù)可以安全地【將一個協(xié)程加入到另一線程的事件循環(huán)中】,并返回一個線程安全的concurrent.futures.Future
對象。你可以在當(dāng)前線程中等待這個 Future 對象,以獲取協(xié)程的執(zhí)行結(jié)果 對于 I/O-bound 任務(wù)或其他長時間運行的操作,可以使用
loop.run_in_executor()
asyncio.to_thread()
方法將I/O-bound任務(wù)委托給別的線程執(zhí)行
以下代碼演示了如何在線程B中正確操作線程A中的future對象
import asyncio
import time
async def main():
# return the loop instance specific to the current thread
loop = asyncio.get_event_loop()
# create an default future
future = loop.create_future()
def set_future_result():
time.sleep(1)
future.set_result("Hello, world!")
print(f"future done: {future.done()}")
# # use common thread to set future -> not allowed since thread unsafe
# import threading
# t = threading.Thread(target=set_future_result)
# t.start()
# use loop.run_in_executor instead
loop.run_in_executor(None, set_future_result)
try:
print("coro main waiting")
result = await future
print("coro main wait done")
print(f"result is {result}")
except Exception as e:
print(f"An error occurred: {e}")
loop = asyncio.new_event_loop()
loop.run_until_complete(main())
loop.close()
asyncio基本用法示例
asyncio就是使用一個loop
對象,對協(xié)程進(jìn)行event loop式執(zhí)行
下面給出一個代碼示例,代碼邏輯為
- 創(chuàng)建一個
loop
對象 -
submit_coroutines_blocking
不斷向loop
提交任務(wù)協(xié)程 - 注意,
submit_coroutines_blocking
是阻塞的,因此通過asyncio.to_thread
委托給另一獨立線程執(zhí)行。asyncio.to_thread
返回的是一個coroutine,需要loop啟動后才能被調(diào)度執(zhí)行 - 通過
loop.run_until_complete
或者loop.run_forever
啟動loop
。(注意,run_forever
表示讓loop
對象一直進(jìn)行事件循環(huán)。調(diào)用loop.run_forever
之前不一定需要先往loop
進(jìn)行create_task塞入任務(wù),可以先空跑起來,然后通過其他手段往loop
里塞任務(wù))
import asyncio
import time
# 任務(wù)協(xié)程
async def my_coroutine(i):
print(f"eventloop thread: Started coroutine {i}")
await asyncio.sleep(5)
print(f"eventloop thread: Finished coroutine {i}")
def submit_coroutines_blocking(loop: asyncio.AbstractEventLoop):
"""
submit_coroutines_blocking 是一個無限循環(huán)
每次循環(huán)將向loop提交一個任務(wù)協(xié)程,并且通過time.sleep來模擬阻塞狀態(tài)
"""
i = 0
try:
while loop.is_running():
# `submit_coroutines_blocking`將在一個單獨的線程中運行,因此不能直接通過`loop.create_task(my_coroutine(i))`訪問原線程的loop對象
# loop.create_task(my_coroutine(i))
# -> use asyncio.run_coroutine_threadsafe instead
asyncio.run_coroutine_threadsafe(my_coroutine(i), loop)
print(f"separated thread: submit coroutine {i} to loop, and going to sleep 2")
time.sleep(2) # 阻塞 2 秒提交一個新的協(xié)程
i += 1
except Exception as e:
print(f"Caught exception: {e}")
loop.stop()
async def submit_coroutines_coro(loop: asyncio.AbstractEventLoop):
# 將阻塞式的submit_coroutines_blocking委托給單獨的線程處理
await asyncio.to_thread(submit_coroutines_blocking, loop)
# await loop.run_in_executor(None, submit_coroutines_blocking, loop) # is ok too
loop = asyncio.new_event_loop()
loop.run_until_complete(submit_coroutines_coro(loop))
#####
# or
# loop.create_task(submit_coroutines_coro(loop))
# loop.run_forever()
#####
輸出如下:
separated thread: submit coroutine 0 to loop, and going to sleep 2
eventloop thread: Started coroutine 0
separated thread: submit coroutine 1 to loop, and going to sleep 2
eventloop thread: Started coroutine 1
separated thread: submit coroutine 2 to loop, and going to sleep 2
eventloop thread: Started coroutine 2
eventloop thread: Finished coroutine 0
separated thread: submit coroutine 3 to loop, and going to sleep 2
eventloop thread: Started coroutine 3
eventloop thread: Finished coroutine 1
separated thread: submit coroutine 4 to loop, and going to sleep 2
eventloop thread: Started coroutine 4
eventloop thread: Finished coroutine 2
separated thread: submit coroutine 5 to loop, and going to sleep 2
eventloop thread: Started coroutine 5
eventloop thread: Finished coroutine 3
...
當(dāng)然,我們也可以借助loop.run_in_executor
把submit_coroutines_blocking
放到一個單獨線程中執(zhí)行
import asyncio
import time
# 任務(wù)協(xié)程
async def my_coroutine(i):
print(f"eventloop thread: Started coroutine {i}")
await asyncio.sleep(5)
print(f"eventloop thread: Finished coroutine {i}")
def submit_coroutines_blocking(loop: asyncio.AbstractEventLoop):
"""
submit_coroutines_blocking 是一個無限循環(huán)
每次循環(huán)將向loop提交一個任務(wù)協(xié)程,并且通過time.sleep來模擬阻塞狀態(tài)
"""
i = 0
try:
while True:
if not loop.is_running():
print("loop is not running, continue")
time.sleep(2) # 阻塞 2 秒提交一個新的協(xié)程
continue
# `submit_coroutines_blocking`將在一個單獨的線程中運行,因此不能直接通過`loop.create_task(my_coroutine(i))`訪問原線程的loop對象
# loop.create_task(my_coroutine(i)) # is wrong -> use asyncio.run_coroutine_threadsafe instead
asyncio.run_coroutine_threadsafe(my_coroutine(i), loop)
print(f"separated thread: submit coroutine {i} to loop, and going to sleep 2")
time.sleep(2) # 阻塞 2 秒提交一個新的協(xié)程
i += 1
except Exception as e:
print(f"Caught exception: {e}")
loop.stop()
loop = asyncio.new_event_loop()
# 開啟一個獨立線程,向loop提交task,線程立刻執(zhí)行
loop.run_in_executor(None, submit_coroutines_blocking, loop)
loop.run_forever()