Python里asyncio原理 異步I/O框架asyncio的事件循環(huán)解析

python中使用asyncio實(shí)現(xiàn)異步編程的核心是事件循環(huán)與協(xié)程的配合,它通過(guò)調(diào)度機(jī)制在i/o等待期間執(zhí)行其他任務(wù)來(lái)提升效率。事件循環(huán)作為任務(wù)調(diào)度員,負(fù)責(zé)注冊(cè)任務(wù)、輪詢事件和執(zhí)行回調(diào),直到所有任務(wù)完成。協(xié)程是異步任務(wù)的基本單位,通過(guò)async def定義并返回協(xié)程對(duì)象,需放入事件循環(huán)中運(yùn)行。await關(guān)鍵字用于掛起當(dāng)前協(xié)程,將控制權(quán)交還事件循環(huán),待被等待的任務(wù)完成后繼續(xù)執(zhí)行。管理多個(gè)任務(wù)時(shí),可通過(guò)asyncio.create_task()將其包裝為任務(wù)并發(fā)調(diào)度,從而避免串行執(zhí)行。常見誤區(qū)包括:直接調(diào)用async函數(shù)而未await、在非異步上下文中調(diào)用異步代碼、混用阻塞與異步函數(shù)等。正確做法是確保異步代碼始終在異步上下文中運(yùn)行,如通過(guò)asyncio.run()啟動(dòng)或嵌套在async函數(shù)內(nèi)。

python中使用asyncio實(shí)現(xiàn)異步編程,核心就在于事件循環(huán)(Event Loop)和協(xié)程(Coroutine)的配合。它并不是讓你的代碼真正“并行”執(zhí)行,而是通過(guò)調(diào)度機(jī)制,讓程序在等待I/O操作時(shí)去做別的事,從而提升整體效率。


事件循環(huán)是啥?它是怎么工作的?

你可以把事件循環(huán)想象成一個(gè)“任務(wù)調(diào)度員”,它負(fù)責(zé)監(jiān)聽、安排和運(yùn)行各種異步任務(wù)。當(dāng)你啟動(dòng)一個(gè)asyncio程序時(shí),首先要?jiǎng)?chuàng)建或獲取一個(gè)事件循環(huán)實(shí)例。

事件循環(huán)的核心工作流程大概是這樣的:

  • 注冊(cè)任務(wù):你把一協(xié)程任務(wù)交給事件循環(huán)。
  • 輪詢事件:事件循環(huán)不斷檢查哪些任務(wù)可以繼續(xù)執(zhí)行(比如某個(gè)網(wǎng)絡(luò)請(qǐng)求已經(jīng)返回結(jié)果)。
  • 執(zhí)行回調(diào):一旦某個(gè)任務(wù)可以繼續(xù)了,就調(diào)用對(duì)應(yīng)的函數(shù)來(lái)處理下一步。
  • 循環(huán)往復(fù),直到所有任務(wù)完成。

這個(gè)過(guò)程聽起來(lái)有點(diǎn)像瀏覽器里的JavaScript事件循環(huán),只不過(guò)Python這邊更明確地暴露給你控制權(quán)。

立即學(xué)習(xí)Python免費(fèi)學(xué)習(xí)筆記(深入)”;


協(xié)程與await關(guān)鍵字是怎么配合事件循環(huán)的?

在asyncio里,協(xié)程是異步任務(wù)的基本單位。你寫一個(gè)async def定義的函數(shù),它不會(huì)立即執(zhí)行,而是返回一個(gè)協(xié)程對(duì)象。

舉個(gè)例子:

async def say_hello():     print("Hello")

這個(gè)函數(shù)你需要把它放進(jìn)事件循環(huán)里才能真正跑起來(lái)。而await的作用就是告訴事件循環(huán):“我現(xiàn)在要等一件事完成,在這期間你可以去干別的。”

比如:

async def main():     await say_hello()

當(dāng)main()被運(yùn)行時(shí),它會(huì)先掛起自己,讓出控制權(quán)給事件循環(huán),然后等say_hello()完成之后再回來(lái)繼續(xù)執(zhí)行。


事件循環(huán)如何管理多個(gè)任務(wù)?

如果你有多個(gè)異步任務(wù),比如同時(shí)發(fā)起多個(gè)http請(qǐng)求,你可以通過(guò)asyncio.create_task()把它們包裝成任務(wù),并交給事件循環(huán)統(tǒng)一調(diào)度。

看個(gè)簡(jiǎn)單例子:

async def task1():     await asyncio.sleep(1)     print("Task 1 done")  async def task2():     await asyncio.sleep(2)     print("Task 2 done")  async def main():     t1 = asyncio.create_task(task1())     t2 = asyncio.create_task(task2())     await t1     await t2  asyncio.run(main())

在這個(gè)例子里,兩個(gè)任務(wù)幾乎是“并發(fā)”運(yùn)行的(注意不是并行),總耗時(shí)約2秒,而不是加起來(lái)3秒。這就是事件循環(huán)調(diào)度的好處。

需要注意幾點(diǎn):

  • create_task()會(huì)自動(dòng)將任務(wù)加入當(dāng)前事件循環(huán)
  • 你不一定要按順序await這些任務(wù),但不await的話主函數(shù)可能提前結(jié)束
  • 如果你不用create_task(),直接await coroutine(),那任務(wù)是串行的

async/await模型有哪些常見誤區(qū)?

很多人剛接觸asyncio時(shí)容易犯幾個(gè)典型錯(cuò)誤:

  • 把a(bǔ)sync def函數(shù)當(dāng)作普通函數(shù)調(diào)用,結(jié)果只是得到了一個(gè)協(xié)程對(duì)象,沒(méi)執(zhí)行
  • 忘記await協(xié)程,導(dǎo)致代碼看起來(lái)“沒(méi)反應(yīng)”
  • 在非異步函數(shù)里直接調(diào)用異步代碼,比如在一個(gè)普通函數(shù)里調(diào)用asyncio.run(),雖然能用但容易出錯(cuò)
  • 混淆阻塞和異步函數(shù),比如用了普通的time.sleep()而不是asyncio.sleep()

所以,記住一點(diǎn):異步代碼必須在異步上下文中運(yùn)行,也就是要么你在async def函數(shù)里,要么你在用asyncio.run()啟動(dòng)入口函數(shù)。


基本上就這些。理解事件循環(huán)和協(xié)程的關(guān)系是關(guān)鍵,剩下的就是在實(shí)際場(chǎng)景中多練習(xí)。

? 版權(quán)聲明
THE END
喜歡就支持一下吧
點(diǎn)贊10 分享