在學(xué)習(xí)asyncio相關(guān)的知識(shí)前,如果有同學(xué)沒(méi)有讀到Python3中yield與yield from詳解的話(huà)深胳,還請(qǐng)先瀏覽一下,以便能對(duì)asyncio有更好的理解。至于如何學(xué)習(xí)asyncio呢参淫?我想從以下幾個(gè)方面來(lái)闡述:
一:asyncio工作原理和重要概念
????????1.1:?事件循環(huán) -??Eventloop
? ? ? ? 1.2:?協(xié)程對(duì)象 -??Coroutine
? ? ? ? 1.3:?Future & Task 對(duì)象
? ? ? ? 1.4:?async/await 原生協(xié)程
? ? ? ? 1.5:?asyncio 如何正確啟動(dòng)
一:?asyncio工作原理和重要概念
那么在學(xué)習(xí)asyncio之前迟蜜,有個(gè)插曲牺弹,其實(shí)你應(yīng)該要知道操作系統(tǒng)中的網(wǎng)絡(luò)編程的相關(guān)知識(shí)喇澡,比如阻塞IO迅栅、非阻塞IO殊校、IO多路復(fù)用晴玖、異步IO等,具體可參考?Linux IO模式及 select、poll呕屎、epoll詳解让簿,對(duì)其有個(gè)大概的理解,方便我們對(duì)asyncio的理解秀睛。下面我們將講解幾個(gè)asyncio 相關(guān)的概念問(wèn)題:
1.1:事件循環(huán) -??Eventloop
Eventloop 可以說(shuō)是 asyncio 應(yīng)用的核心尔当,是中央總控。Eventloop 實(shí)例提供了注冊(cè)蹂安、取消和執(zhí)行任務(wù)和回調(diào)的方法椭迎。程序開(kāi)啟一個(gè)無(wú)限的循環(huán),程序員會(huì)把一些函數(shù)(協(xié)程)注冊(cè)到事件循環(huán)上田盈。當(dāng)滿(mǎn)足事件發(fā)生的時(shí)候畜号,調(diào)用相應(yīng)的協(xié)程函數(shù)。
把一些異步函數(shù) (就是任務(wù)允瞧,Task简软,一會(huì)就會(huì)說(shuō)到) 注冊(cè)到這個(gè)事件循環(huán)上,事件循環(huán)會(huì)循環(huán)執(zhí)行這些函數(shù) (但同時(shí)只能執(zhí)行一個(gè))述暂,當(dāng)執(zhí)行到某個(gè)函數(shù)時(shí)痹升,如果它正在等待 I/O 返回,事件循環(huán)會(huì)暫停它的執(zhí)行去執(zhí)行其他的函數(shù)畦韭;當(dāng)某個(gè)函數(shù)完成 I/O 后會(huì)恢復(fù)疼蛾,下次循環(huán)到它的時(shí)候繼續(xù)執(zhí)行。因此艺配,這些異步函數(shù)可以協(xié)同 (Cooperative) 運(yùn)行:這就是事件循環(huán)的目標(biāo)据过。
(1): 事件循環(huán)是執(zhí)行我們的異步代碼并決定如何在異步函數(shù)之間切換的對(duì)象。如果某個(gè)協(xié)程在等待某些資源妒挎,我們需要暫停它的執(zhí)行绳锅,在事件循環(huán)中注冊(cè)這個(gè)事件,以便當(dāng)事件發(fā)生的時(shí)候酝掩,能再次喚醒該協(xié)程的執(zhí)行鳞芙。
(2): 運(yùn)行異步函數(shù)我們首先需要?jiǎng)?chuàng)建一個(gè)協(xié)程,然后創(chuàng)建future或task對(duì)象期虾,將它們添加到事件循環(huán)中原朝,到目前為止,我們的異步函數(shù)中沒(méi)有任何代碼被執(zhí)行過(guò)镶苞,只有調(diào)用loop.run_until_completed啟動(dòng)事件循環(huán)喳坠,才會(huì)開(kāi)始執(zhí)行future或task對(duì)象,loop.run_until_completed會(huì)阻塞程序直到所有的協(xié)程對(duì)象都執(zhí)行完畢茂蚓。
備注:事件循環(huán)的本質(zhì)就是通過(guò) await Coroutinec_or_task_?or_future 將程序的控制權(quán)還給CPU, 然后CPU執(zhí)行其它的任務(wù)壕鹉,當(dāng)再次遇到 await xxx剃幌,CPU又獲取到了控制權(quán),再次選擇執(zhí)行其它任務(wù)晾浴。當(dāng)某個(gè)任務(wù)(或稱(chēng)事件)完成了负乡,喚醒CPU, 讓其獲取控制權(quán)執(zhí)行后面的代碼。
1脊凰、事件循環(huán)是在線(xiàn)程中執(zhí)行
2抖棘、從隊(duì)列中取得任務(wù)
3、每個(gè)任務(wù)在協(xié)程中執(zhí)行下一步動(dòng)作
4狸涌、如果在一個(gè)協(xié)程中調(diào)用另一個(gè)協(xié)程(await?)切省,會(huì)觸發(fā)上下文切換,掛起當(dāng)前協(xié)程帕胆,并保存現(xiàn)場(chǎng)環(huán)境(變量数尿,狀態(tài)),然后載入被調(diào)用協(xié)程
5惶楼、如果協(xié)程的執(zhí)行到阻塞部分(阻塞I/O右蹦,Sleep),當(dāng)前協(xié)程會(huì)掛起歼捐,并將控制權(quán)返回到線(xiàn)程的消息循環(huán)中何陆,然后消息循環(huán)繼續(xù)從隊(duì)列中執(zhí)行下一個(gè)任務(wù)...以此類(lèi)推
6、隊(duì)列中的所有任務(wù)執(zhí)行完畢后豹储,消息循環(huán)返回第一個(gè)任務(wù)
? 1.2:?協(xié)程對(duì)象 -??Coroutine
協(xié)程對(duì)象贷盲,指一個(gè)使用async關(guān)鍵字定義的函數(shù),它的調(diào)用不會(huì)立即執(zhí)行函數(shù)剥扣,而是會(huì)返回一個(gè)協(xié)程對(duì)象营勤。協(xié)程對(duì)象需要注冊(cè)到事件循環(huán)另伍,由事件循環(huán)調(diào)用。協(xié)程 (Coroutine) 本質(zhì)上是一個(gè)函數(shù).
1:async 關(guān)鍵定義的函數(shù)(稱(chēng)協(xié)程),關(guān)鍵字await 只能在協(xié)程內(nèi)部中使用玖院,?其它地方不能使用
2:協(xié)程內(nèi)部不能使用 yield from,報(bào)語(yǔ)法錯(cuò)誤锯梁,但可以使用 yield (但很少使用, 自python3.7之后只有使用 async/await定義的函數(shù)才是原生協(xié)程璧瞬,會(huì)逐漸拋棄yield與asyncio.coroutine這種zai Python 3.5裝飾器生成的協(xié)程)
3:被 async 關(guān)鍵定義的協(xié)程嗅绸,實(shí)際上是一個(gè)Coroutine對(duì)象,而Coroutine又繼承Awaitable?
????????from collections.abc import Coroutine, Awaitable
4:定義協(xié)程示例:
????????async? def? async_test():
? ? ? ? ????????await asyncio.sleep(2)
? ? ? ? ????????......
Awaitable 對(duì)象: await?關(guān)鍵字用于將程序控制權(quán)移交給事件循環(huán)并中斷當(dāng)前協(xié)程的執(zhí)行断国。它有以下幾個(gè)使用規(guī)則:
a: 只能用在由?async def?修飾的函數(shù)中贤姆,在普通函數(shù)中使用會(huì)拋出異常。
b: 調(diào)用一個(gè)協(xié)程函數(shù)后稳衬,就必須等待其執(zhí)行完成并返回結(jié)果霞捡。
c: await func()?中的?func()?必須是一個(gè)?awaitable?對(duì)象。即一個(gè)協(xié)程函數(shù)或者一個(gè)在內(nèi)部實(shí)現(xiàn)了?__await__()?方法的對(duì)象薄疚,該方法會(huì)返回一個(gè)生成器
Awaitable 對(duì)象包含協(xié)程碧信、Task 和 Future 等赊琳。
? 1.3:??Future & Task 對(duì)象
Future 對(duì)象:?
代表將來(lái)執(zhí)行或沒(méi)有執(zhí)行的任務(wù)的結(jié)果,它和task上沒(méi)有本質(zhì)的區(qū)別音婶。
它代表了一個(gè)「未來(lái)」對(duì)象慨畸,異步操作結(jié)束后會(huì)把最終結(jié)果設(shè)置到這個(gè) Future 對(duì)象上莱坎。Future 是對(duì)協(xié)程的封裝衣式,不過(guò)日常開(kāi)發(fā)基本是不需要直接用這個(gè)底層 Future 類(lèi)的。
Future對(duì)象封裝了一個(gè)未來(lái)會(huì)被計(jì)算的可調(diào)用的異步執(zhí)行對(duì)象檐什,他們能被放入隊(duì)列碴卧,他們的狀態(tài)、結(jié)果或者異常能被查詢(xún)乃正。 Future對(duì)象有一個(gè)result屬性住册,用于存放未來(lái)的執(zhí)行結(jié)果。還有個(gè)set_result()方法瓮具,是用于設(shè)置result的荧飞,并且會(huì)在給result綁定值以后運(yùn)行事先給Future對(duì)象添加的回調(diào)∶常回調(diào)是通過(guò)Future對(duì)象的add_done_callback()方法添加的叹阔。
重要的是Future對(duì)象不能被我們創(chuàng)建,只能被異步框架創(chuàng)建传睹,有兩種方法:
# 該函數(shù)在 Python 3.7 中被加入耳幢,更加高層次的函數(shù),返回Task對(duì)象
future1= asyncio.create_task(my_coroutine)
# 在Python 3.7 之前欧啤,是更加低級(jí)的函數(shù)睛藻,返回Future對(duì)象或者Task對(duì)象
future2= asyncio.ensure_future(my_coroutine)
第一種方法在循環(huán)中添加一個(gè)協(xié)程并返回一個(gè)task對(duì)象,task對(duì)象是future的子類(lèi)型邢隧。第二種方法非常相似店印,當(dāng)傳入?yún)f(xié)程對(duì)象時(shí)返回一個(gè)Task對(duì)象,唯一的區(qū)別是它也可以接受Future對(duì)象或Task對(duì)象倒慧,在這種情況下它不會(huì)做任何事情并且返回Future對(duì)象或者Task對(duì)象不變吱窝。
Future對(duì)象有幾個(gè)狀態(tài):
Pending:就緒
Running:運(yùn)行
Done:完成
Cancelled:取消
創(chuàng)建Future對(duì)象的時(shí)候,狀態(tài)為pending迫靖,事件循環(huán)調(diào)用執(zhí)行的時(shí)候就是running院峡,調(diào)用完畢就是done,如果需要取消Future對(duì)象的調(diào)度執(zhí)行系宜,可調(diào)用Future對(duì)象的cancel()函數(shù)照激。
除此之外,F(xiàn)uture對(duì)象還有下面一些常用的方法:
result():立即返回Future對(duì)象運(yùn)行結(jié)果或者拋出執(zhí)行時(shí)的異常盹牧,沒(méi)有timeout參數(shù)俩垃,如果Future沒(méi)有完成励幼,不會(huì)阻塞等待結(jié)果,而是直接拋出InvalidStateError異常口柳。最好的方式是通過(guò)await獲取運(yùn)行結(jié)果苹粟,await會(huì)自動(dòng)等待Future完成返回結(jié)果,也不會(huì)阻塞事件循環(huán)跃闹,因?yàn)樵赼syncio中嵌削,await被用來(lái)將控制權(quán)返回給事件循環(huán)。
done():非阻塞的返回Future對(duì)象是否成功取消或者運(yùn)行結(jié)束或被設(shè)置異常望艺,而不是查看future是否已經(jīng)執(zhí)行完成苛秕。
cancelled():判斷Future對(duì)象是否被取消。
add_done_callback():傳入一個(gè)可回調(diào)對(duì)象找默,當(dāng)Future對(duì)象done時(shí)被調(diào)用艇劫。
exception():獲取Future對(duì)象中的異常信息,只有當(dāng)Future對(duì)象done時(shí)才會(huì)返回惩激。
get_loop():獲取當(dāng)前Future對(duì)象綁定的事件循環(huán)店煞。
需要注意的是,當(dāng)在協(xié)程內(nèi)部引發(fā)未處理的異常時(shí)风钻,它不會(huì)像正常的同步編程那樣破壞我們的程序顷蟀,相反,它存儲(chǔ)在future內(nèi)部魄咕,如果在程序退出之前沒(méi)有處理異常衩椒,則會(huì)出現(xiàn)以下錯(cuò)誤:
Task? exception? was? never? retrieved
有兩種方法可以解決此問(wèn)題,在訪(fǎng)問(wèn)future對(duì)象的結(jié)果時(shí)捕獲異诚迹或調(diào)用future對(duì)象的異常函數(shù):
try:
? ? ? ? # 調(diào)用結(jié)果時(shí)捕獲異常
? ? ? ? my_promise.result()
catchException:
????????pass
# 獲取在協(xié)程執(zhí)行過(guò)程中拋出的異常
my_promise.exception()
Task對(duì)象
一個(gè)協(xié)程對(duì)象就是一個(gè)原生可以?huà)炱鸬暮瘮?shù)毛萌,任務(wù)則是對(duì)協(xié)程進(jìn)一步封裝,其中包含任務(wù)的各種狀態(tài)喝滞。Task 對(duì)象是 Future 的子類(lèi)阁将,它將 coroutine 和 Future 聯(lián)系在一起,將 coroutine 封裝成一個(gè) Future 對(duì)象右遭。
Future 是協(xié)程的封裝做盅,F(xiàn)uture 對(duì)象提供了很多任務(wù)方法 (如完成后的回調(diào)、取消窘哈、設(shè)置任務(wù)結(jié)果等等)吹榴,但是開(kāi)發(fā)者并不需要直接操作 Future 這種底層對(duì)象,而是用 Future 的子類(lèi) Task 協(xié)同的調(diào)度協(xié)程以實(shí)現(xiàn)并發(fā)滚婉。
Task對(duì)象被用來(lái)在事件循環(huán)中運(yùn)行協(xié)程图筹。如果一個(gè)協(xié)程在等待一個(gè)Future對(duì)象,Task對(duì)象會(huì)掛起該協(xié)程的執(zhí)行并等待該Future對(duì)象完成。當(dāng)該Future對(duì)象完成远剩,被暫停的協(xié)程將恢復(fù)執(zhí)行扣溺。事件循環(huán)使用協(xié)作調(diào)度: 一個(gè)事件循環(huán)每次運(yùn)行一個(gè)Task對(duì)象。當(dāng)一個(gè)Task對(duì)象等待一個(gè)Future對(duì)象完成時(shí)瓜晤,該事件循環(huán)會(huì)運(yùn)行其他Task锥余、回調(diào)或執(zhí)行IO操作。
使用高層級(jí)的asyncio.create_task()函數(shù)來(lái)創(chuàng)建Task對(duì)象痢掠,也可用低層級(jí)的loop.create_task()或ensure_future()函數(shù)驱犹。不建議手動(dòng)實(shí)例化 Task 對(duì)象。
Python 的例子:
import asyncio
import time
async? def? compute(x, y):
????????print("Compute {} + {}...".format(x, y))
? ? ? ? await? asyncio.sleep(2.0)
? ? ? ? return x+yasync? def? print_sum(x, y):
? ? ? ? result =await compute(x, y)
? ? ? ? print("{} + {} = {}".format(x, y, result))start = time.time()?????
loop = asyncio.get_event_loop()?
tasks = [?
? ? ? ? asyncio.ensure_future(print_sum(0,0)),
? ? ? ? asyncio.ensure_future(print_sum(1,1)),
? ? ? ? asyncio.ensure_future(print_sum(2,2)),
]
loop.run_until_complete(asyncio.wait(tasks))?
loop.close()?
print("Total elapsed time {}".format(time.time() - start))
上面的代碼的執(zhí)行流程是:
詳細(xì)的流程如下:
?1.4:?async/await 原生協(xié)程
Python設(shè)計(jì)者們?cè)?3.5 中新增了async/await語(yǔ)法(PEP 492)志群,將協(xié)程作為原生Python語(yǔ)言特性着绷,并且將他們與生成器明確的區(qū)分開(kāi)蛔钙。它避免了生成器/協(xié)程中間的混淆锌云,方便編寫(xiě)出不依賴(lài)于特定庫(kù)的協(xié)程代碼,稱(chēng)之為原生協(xié)程吁脱。async/await 和 yield from這兩種風(fēng)格的協(xié)程底層復(fù)用共同的實(shí)現(xiàn)桑涎,而且相互兼容。在Python 3.6 中asyncio庫(kù)“轉(zhuǎn)正”兼贡,不再是實(shí)驗(yàn)性質(zhì)的攻冷,成為標(biāo)準(zhǔn)庫(kù)的正式一員。
Python 3.5 添加了types.coroutine修飾器遍希,也可以像 asyncio.coroutine 一樣將生成器標(biāo)記為協(xié)程等曼。你可以用 async def 來(lái)定義一個(gè)協(xié)程函數(shù),雖然這個(gè)函數(shù)不能包含任何形式的 yield 語(yǔ)句凿蒜;只有 return 和 await 可以從協(xié)程中返回值禁谦。
?* 句法?async def?引入了原生協(xié)程或者說(shuō)異步生成器。表達(dá)式?async with?和?async for?也是允許的废封,稍后就可以看到州泊。
* 關(guān)鍵字?await?將控制器傳遞給時(shí)間循環(huán)。(掛起當(dāng)前運(yùn)行的協(xié)程漂洋。)Python執(zhí)行的時(shí)候遥皂,在g()?函數(shù)范圍內(nèi)如果遇到表達(dá)式?await f(),就是?await?在告訴事件循環(huán)“掛起?g()?函數(shù)刽漂,直到?f()?返回結(jié)果演训,在此期間,可以運(yùn)行其他函數(shù)贝咙⊙颍”
上述第二點(diǎn)在代碼中大致如下:
async def g():
? ? ? ? # 暫停,知道f()在返回到g()
? ? ? ? await? f()
關(guān)于要不要用?async/await颈畸,以及何時(shí)使用乌奇,如何使用没讲,都有一套嚴(yán)格的規(guī)則。無(wú)論你是在使用語(yǔ)法還是已經(jīng)使用?async/await礁苗,這些規(guī)則都會(huì)很方便:
1. 協(xié)程是引入了?async def?的函數(shù)爬凑。你可能會(huì)用到?await,return?或者?yield试伙,但是這些都是可選的嘁信。Python允許使用?async def noop(): pass?聲明:
????????1.1. 使用?await?與?return?的組合創(chuàng)建協(xié)程函數(shù)。想要調(diào)用一個(gè)協(xié)程函數(shù)疏叨,必須使用?await等待返回結(jié)果潘靖。
????????1.2. 在?async def?代碼塊中使用?yield?的情況并不多見(jiàn)(只有Python的近期版本才可用)。當(dāng)你使用?async for?進(jìn)行迭代的時(shí)候蚤蔓,會(huì)創(chuàng)建一個(gè)異步生成器卦溢。暫時(shí)先忘掉異步生成器,將目光放在使用?await?與?return?的組合創(chuàng)建協(xié)程函數(shù)的語(yǔ)法上秀又。
????????1.3. 在任何使用?async def?定義的地方都不可以使用?yield from单寂,這會(huì)引發(fā)異常?SyntaxError。2. 一如在?def?定義的函數(shù)之外使用?yield?會(huì)引發(fā)異常?SyntaxError吐辙,在?async def?定義的協(xié)程之外使用?await?也會(huì)引發(fā)異常?SyntaxError宣决。你只能在協(xié)程內(nèi)部使用?await。
這里有一個(gè)簡(jiǎn)介的例子昏苏,總結(jié)了上面的幾條規(guī)則:
最后尊沸,當(dāng)你使用?await f()?時(shí),要求?f()?是一個(gè)可等待的對(duì)象贤惯。但這并沒(méi)有什么用⊥葑ǎ現(xiàn)在,只需要知道可等待對(duì)象要么是(1)其他的協(xié)程,要么就是(2)定義了?.await()?函數(shù)且返回迭代器的對(duì)象救巷。如果你正在編寫(xiě)程序壶熏,絕大多數(shù)情況只需要關(guān)注案例#1。
這給我們帶來(lái)了一些技術(shù)上的差異:將一個(gè)函數(shù)標(biāo)記為協(xié)程的舊的一個(gè)方式是使用?@asyncio.coroutine?裝飾一個(gè)普通的函數(shù)浦译。這是基于生成器的協(xié)程棒假。但是這種方式自Python 3.5中出現(xiàn)了?async/await?語(yǔ)法后就已經(jīng)過(guò)時(shí)了。
下面兩個(gè)協(xié)程基本上是等價(jià)的(都是可等待的)精盅,但第一個(gè)是基于生成器的帽哑,而第二個(gè)是原生協(xié)程。
如果你寫(xiě)代碼的時(shí)候更趨向于顯式聲明而不是隱式聲明叹俏,那么最好是使用原生協(xié)程妻枕。基于生成器的協(xié)程將會(huì)在Python 3.10版本移除。
本教程的后半部分屡谐,我們會(huì)再涉及一些基于生成器的協(xié)程的優(yōu)點(diǎn)述么。為了使協(xié)程成為Python中獨(dú)立的標(biāo)準(zhǔn)功能,并與常規(guī)生成器區(qū)分開(kāi)愕掏,以減少歧義度秘,Python引入?async/await。
不要沉迷于基于生成器的協(xié)程饵撑,它已經(jīng)被?async/await?取代了剑梳。如果你要使用?async/await?語(yǔ)法的話(huà),注意它的一些特有的規(guī)則(比如滑潘,await?不能用于基于生成器的協(xié)程)垢乙,這些規(guī)則很大程度上與基于生成器的協(xié)程不兼容。
協(xié)程的主要屬性包括:
1:async def函數(shù)始終為協(xié)程语卤,即使它不包含await表達(dá)式追逮。
2:如果在async函數(shù)中使用yield或者yield from表達(dá)式會(huì)產(chǎn)生SyntaxError錯(cuò)誤。
3:在內(nèi)部粱侣,引入了兩個(gè)新的代碼對(duì)象標(biāo)記:
????????CO_COROUTINE用于標(biāo)記原生協(xié)程(和新語(yǔ)法一起定義)
????????CO_ITERABLE_COROUTINE用于標(biāo)記基于生成器的協(xié)程羊壹,兼容原生協(xié)程蓖宦。(通過(guò)types.coroutine()函數(shù)設(shè)置)4:常規(guī)生成器在調(diào)用時(shí)會(huì)返回一個(gè)genertor對(duì)象齐婴,同理,協(xié)程在調(diào)用時(shí)會(huì)返回一個(gè)coroutine對(duì)象稠茂。
5:協(xié)程不再拋出StopIteration異常柠偶,而是替代為RuntimeError。常規(guī)生成器實(shí)現(xiàn)類(lèi)似的行為需要進(jìn)行引入future(PEP-3156)
6:當(dāng)協(xié)程進(jìn)行垃圾回收時(shí)睬关,一個(gè)從未被await的協(xié)程會(huì)拋出RuntimeWarning異常
types.coroutine():在types模塊中新添加了一個(gè)函數(shù)coroutine(fn)用于asyncio中基于生成器的協(xié)程與本PEP中引入的原生攜協(xié)程互通诱担。使用它,“生成器實(shí)現(xiàn)的協(xié)程”和“原生協(xié)程”之間可以進(jìn)行互操作电爹。
@types.coroutine
def? process_data(db):
????????data =yieldfromread_data(db)
? ? ? ? ...
這個(gè)函數(shù)將生成器函數(shù)對(duì)象設(shè)置CO_ITERABLE_COROUTINE標(biāo)記蔫仙,將返回對(duì)象變?yōu)閏oroutine對(duì)象。如果fn不是一個(gè)生成器函數(shù)丐箩,那么它會(huì)對(duì)其進(jìn)行封裝摇邦。如果它返回一個(gè)生成器,那么它會(huì)封裝一個(gè)awaitable代理對(duì)象屎勘。
注意:CO_COROUTINE標(biāo)記不能通過(guò)types.coroutine()進(jìn)行設(shè)置施籍,這就可以將新語(yǔ)法定義的原生協(xié)程與基于生成器的協(xié)程進(jìn)行區(qū)分。
await與yield from相似概漱,await關(guān)鍵字的行為類(lèi)似標(biāo)記了一個(gè)斷點(diǎn)丑慎,掛起協(xié)程的執(zhí)行直到其他awaitable對(duì)象完成并返回結(jié)果數(shù)據(jù)。它復(fù)用了yield from的實(shí)現(xiàn),并且添加了額外的驗(yàn)證參數(shù)竿裂。await只接受以下之一的awaitable對(duì)象:
一個(gè)原生協(xié)程函數(shù)返回的原生協(xié)程對(duì)象玉吁。
一個(gè)使用types.coroutine()修飾器的函數(shù)返回的基于生成器的協(xié)程對(duì)象。這種用法已經(jīng)被廢棄了
一個(gè)包含返回迭代器的await方法的對(duì)象腻异。
協(xié)程鏈:協(xié)程的一個(gè)關(guān)鍵特性是它們可以組成協(xié)程鏈诈茧,就像函數(shù)調(diào)用鏈一樣,一個(gè)協(xié)程對(duì)象是awaitable的捂掰,因此其他協(xié)程可以await另一個(gè)協(xié)程對(duì)象敢会。
任意一個(gè)yield from鏈都會(huì)以一個(gè)yield結(jié)束,這是Future實(shí)現(xiàn)的基本機(jī)制这嚣。因此鸥昏,協(xié)程在內(nèi)部中是一種特殊的生成器。每個(gè)await最終會(huì)被await調(diào)用鏈條上的某個(gè)yield語(yǔ)句掛起姐帚。
關(guān)于基于生成器的協(xié)程和async定義的原生協(xié)程之間的差異吏垮,關(guān)鍵點(diǎn)是只有基于生成器的協(xié)程可以真正的暫停執(zhí)行并強(qiáng)制性返回給事件循環(huán)。所以每個(gè)await最終會(huì)被await調(diào)用鏈條上的某個(gè)由types.coroutine()裝飾的包含yield語(yǔ)句的協(xié)程函數(shù)掛起罐旗。
為了啟用協(xié)程的這一特點(diǎn)膳汪,一個(gè)新的魔術(shù)方法__await__被添加進(jìn)來(lái)。在asyncio中九秀,對(duì)于對(duì)象在await語(yǔ)句啟用Future對(duì)象只需要添加await?=?iter這行到asyncio.Future類(lèi)中遗嗽。帶有await方法的對(duì)象也叫做Future-like對(duì)象。
另外還新增了異步上下文管理 async with 和異步迭代器 async for鼓蜒。異步生成器和異步推導(dǎo)式都讓迭代變得并發(fā)痹换,他們所做的只是提供同步對(duì)應(yīng)的外觀(guān),但是有問(wèn)題的循環(huán)能夠放棄對(duì)事件循環(huán)的控制都弹,以便運(yùn)行其他協(xié)程娇豫。
關(guān)于何時(shí)以及如何能夠和不能使用async / await,有一套嚴(yán)格的規(guī)則:
使用async關(guān)鍵字創(chuàng)建一個(gè)協(xié)程函數(shù)畅厢,里面包含await或者return冯痢,調(diào)用協(xié)程函數(shù),必須使用await獲得函數(shù)返回結(jié)果框杜。
在async異步函數(shù)中使用yield并不常見(jiàn)浦楣,這會(huì)創(chuàng)建一個(gè)異步生成器,可以使用async for來(lái)迭代異步生成器霸琴。
在async異步函數(shù)中使用yield from會(huì)拋出語(yǔ)法錯(cuò)誤椒振。同樣在普通函數(shù)中使用await也是語(yǔ)法錯(cuò)誤。
Python 3.5中async/await的工作機(jī)制
? 1.5:?asyncio 如何正確啟動(dòng)
協(xié)程完整的工作流程是這樣的
定義/創(chuàng)建協(xié)程對(duì)象
將協(xié)程轉(zhuǎn)為task任務(wù)
定義事件循環(huán)對(duì)象容器
將task任務(wù)扔進(jìn)事件循環(huán)對(duì)象中觸發(fā)
1.5.1 錯(cuò)誤姿勢(shì):看似是異步梧乘,其實(shí)是同步
async def coro_a():? ? ????
????????print("Suspending coro_a")????????
????????await asyncio.sleep(3)????????
????????print("running coro_a")????????
????????return 100async def coro_b():????
????????print("Suspending coro_b")???
????????await asyncio.sleep(1)????
????????print("running coro_b")????
????????return [1, 23, 45]
async def sync_run():
? ? ????""" 其實(shí)是同步執(zhí)行 """
????????await coro_a()
????????await coro_b()def show_perf(func):
????????start = time.perf_counter()
????????asyncio.run(func())
????????print(f'{func.__name__} Cost: {time.perf_counter() - start}')>>> show_perf(sync_run) # 同步
>>>?Suspending coro_a
>>>?running coro_a
>>>?Suspending coro_b
>>>?running coro_b
>>>?ync_run Cost: 4.0023612同步的原因解析:運(yùn)行?asyncio.run(func()) 后澎迎,將?func() 轉(zhuǎn)化成future對(duì)象庐杨,并放入事件循環(huán)中,隨后事件循環(huán)執(zhí)行的是sync_run() 里面的代碼夹供,程序運(yùn)行至?await coro_a()處灵份,暫停并阻塞,并將CPU控制權(quán)移交出去哮洽,直到coro_a() 執(zhí)行完成并重新獲取CPU控制權(quán)執(zhí)行下面的代碼coro_b()填渠,一直如此,待整個(gè)程序結(jié)束鸟辅, 所以這種和同步?jīng)]什么區(qū)別氛什。
(1):await coro? 作如上解釋
(2):await task_or_future 不會(huì)阻塞
1.5.2 正確姿勢(shì)一:使用asyncio.gather 或?asyncio.wait
async def async_run():
? ??????await asyncio.gather(coro_a(), coro_b()) ???? # 異步
? ? ? ? # await asyncio.wait([coro_a(), coro_b()])? ? ? ? ?# 異步 Py3.11 will removal asyncio.wait>>>?show_perf(async_run) # 異步
>>>?Suspending coro_a
>>>?Suspending coro_b
>>>?running coro_b
>>>?running coro_a
>>>?async_run Cost: 3.0031817
asyncio.gather
gather的參數(shù)為 coroutines_or_futures, 即協(xié)程 或 task 或 future 的可變參數(shù):
(1):?tasks =?await asyncio.gather(*[coro1, coro2, coro3])? |?asyncio.gather(coro1,?coro2,?coro3)
(2):? tasks = awaitasyncio.gather(*[task1, task2,task3])? |??asyncio.gather( task1,task2,task3)
(3):? tasks = awaitasyncio.gather(*[futu1, futu2,futu3])? |???asyncio.gather( futu1,?futu2,?futu3)
(4):? tasks = awaitasyncio.gather(*[coro1,?task2,?futu3])? |???asyncio.gather(?coro1?,??task2,?futu3)返回的是已完成Task的result。
asyncio.wait
wait的參數(shù)為 fs, 即協(xié)程 或 task 或?future? 的單個(gè)列表或元祖:
(1):??tasks =??await?asyncio.wait([coro1,?coro2,?coro3])?
(2):??tasks =?await?asyncio.wait([task1, task2,task3])? ? ?
(3):? tasks =?await?asyncio.wait([futu1,?futu2,?futu3])? ? ?
(4):?tasks =?await?asyncio.wait([coro1,??task2,?futu3])? ?async.wait會(huì)返回兩個(gè)值:done和pending匪凉,done為已完成的協(xié)程Task枪眉,pending為超時(shí)未完成的協(xié)程Task,需通過(guò)future.result調(diào)用Task的result再层。
1.5.3 正確姿勢(shì)二:await task_or_future
async def async_run():
? ??????# 異步
? ??????task_a = asyncio.create_task(coro_a())
????????task_b = asyncio.create_task(coro_b())
????????await task_a
????????await task_b>>>?show_perf(async_run) # 異步OK
>>>?Suspending coro_a
>>>?Suspending coro_b
>>>??running coro_b
>>>?running coro_aasync_run
>>>?Cost: 3.0027209000000004
1.5.4 錯(cuò)誤姿勢(shì)三:await task_or_future贸铜,?直接await task不會(huì)對(duì)并發(fā)有幫助
async def async_run():
? ??????asyncio.create_task(coro_a())
? ??????asyncio.create_task(coro_b())>>>?show_perf(async_run) # 同步
>>>?Suspending coro_a
>>>?running coro_a
>>>?Suspending coro_b
>>>?running coro_basync_run
>>>?Cost: 4.0028486
1.5.5 正確姿勢(shì)四:先 await coro, 再 await? task_or_future
async def async_run():
? ???????# 也可以用:asyncio.ensure_future(coro_a()) 或者?loop.create_task(coro_a())
? ??????task_a= asyncio.create_task(coro_a())? ? ? ? ?#??py3.7推薦用法
? ??????await coro_b()? ? ? ? # 先協(xié)程
? ??????await task_a? ? ? ? ? ? # 再task>>>?show_perf(async_run)????? # 異步OK
>>>?Suspending coro_b
>>>?Suspending coro_a
>>>?running coro_b
>>>?running coro_aasync_run
>>>?Cost: 3.0015209======================錯(cuò)誤姿勢(shì)=======================
? async def async_run():? ??????
? ??????# 也可以用:asyncio.ensure_future(coro_a()) 或者?loop.create_task(coro_a())
????????task_a?= asyncio.create_task(coro_a())?
????????await task_a? ? ? ? ? ? # ?先task?
????????await coro_b()? ? ? ? # 再協(xié)程? ? ? ??>>>?show_perf(async_run)? ? ? # 同步
>>>?async_run Cost: 4.004120800000001
======================?錯(cuò)誤姿勢(shì)?=======================
疑問(wèn):正常來(lái)說(shuō)一般 await coro_task_or_future 程序會(huì)掛起,將cpu控制權(quán)移交給其他程序聂受,但是在一個(gè)協(xié)程里多次直接 await有次序之分蒿秦,那么對(duì)于 await 后程序掛起的理論就不能完全按上文提到的那樣理解,await究其機(jī)制到此我還并未真正探究明白蛋济,等后面補(bǔ)上棍鳖,暫時(shí)只要能區(qū)分1.5.1 、 1.5.3瘫俊、1.5.4鹊杖、1.5.5 中各種姿勢(shì)的對(duì)錯(cuò)與否即可