Python3中asyncio異步詳解一

在學(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+y

async? 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異步編程詳解

Python 3.5中async/await的工作機(jī)制

Python Async/Await入門(mén)指南

? 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 100

async 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ò)與否即可

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市扛芽,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌积瞒,老刑警劉巖川尖,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異茫孔,居然都是意外死亡叮喳,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)缰贝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)馍悟,“玉大人,你說(shuō)我怎么就攤上這事剩晴÷嘀洌” “怎么了侵状?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)毅整。 經(jīng)常有香客問(wèn)我趣兄,道長(zhǎng),這世上最難降的妖魔是什么悼嫉? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任艇潭,我火速辦了婚禮,結(jié)果婚禮上戏蔑,老公的妹妹穿的比我還像新娘蹋凝。我一直安慰自己,他們只是感情好总棵,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布仙粱。 她就那樣靜靜地躺著,像睡著了一般彻舰。 火紅的嫁衣襯著肌膚如雪伐割。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,036評(píng)論 1 285
  • 那天刃唤,我揣著相機(jī)與錄音隔心,去河邊找鬼。 笑死尚胞,一個(gè)胖子當(dāng)著我的面吹牛硬霍,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播笼裳,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼唯卖,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了躬柬?” 一聲冷哼從身側(cè)響起拜轨,我...
    開(kāi)封第一講書(shū)人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎允青,沒(méi)想到半個(gè)月后橄碾,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡颠锉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年法牲,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片琼掠。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡拒垃,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出瓷蛙,到底是詐尸還是另有隱情悼瓮,我是刑警寧澤戈毒,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站谤牡,受9級(jí)特大地震影響副硅,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜翅萤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一恐疲、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧套么,春花似錦培己、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至玷室,卻和暖如春零蓉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背穷缤。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工敌蜂, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人津肛。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓章喉,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親身坐。 傳聞我的和親對(duì)象是個(gè)殘疾皇子秸脱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容