python異步asyncio測試

協程框架基礎

任何一個協程框架都首先必須是一個異步框架,asyncio也不例外。一個異步框架通常主要包括事件循環、事件隊列、polling、timer隊列,所有的異步框架皆不例外,asyncio也是如此。事件循環是實際啓動之後執行的代碼,事件隊列用來向事件循環發送要執行的任務,polling使用multiplexing技術(如select或epoll)用來監控socket等IO活動,timer隊列保存定時器,一般是個最小堆。

執行過程以asyncio爲例,asyncio的事件隊列裏面就是普通的callable,callable執行的時候調用asyncio的其他接口配置polling或者timer隊列或者發送更多callable到事件隊列裏。polling或者timer隊列會將socket活動和計時器關聯到不同的回調,這個回調不是立即執行的,而是延遲迴調,也就是不直接調用而是放進事件隊列裏,等着事件循環去調用。事件循環(也就是所謂EventLoop)開始的時候,會不斷從事件隊列裏取callable,然後一個一個call過去,call完換下一個;事件隊列裏沒有了,就去timer隊列裏取一個最近的timer作爲超時時間,去調用polling,直到任意socket活動,或者超時爲止,然後根據不同的條件,將socket的回調放到事件隊列裏,從頭開始執行。這個過程描述下來,是不是感覺很簡單?

那這麼簡單的結構怎麼實現異步編程呢?原理也很簡單,每個callable都是一小份工作,當這部分工作做完之後,會等待下一個條件:如果要等socket,就設置polling回調;如果要等超時時間,就設置timer;如果要等其他callable完成,就使用同步對象的callback接口(後面會講的Future);如果要同時等多個條件,就把所有的回調都設置上。通過這些回調不斷觸發,就能實現異步編程的目的。

基於回調的代碼結構在這件事情上就比較直白,直接將回調函數設置成polling或者timer或者同步對象的callback就行了。

Future基礎

Future是什麼呢?基本的Future就是一個回調管理器,任何程序都可以通過add_callback的接口爲這個Future設置一個回調函數,在Future完成時會調用這個回調函數。Future剛創建的時候是未完成狀態,有一個接口set_result可以將Future設置爲完成狀態,很顯然實際上也是這個set_result接口調用了已經註冊的那些callback,很簡單很弱智(simple and stupid,stupid對於設計來說是一個褒義詞)的設計,說穿了就是宣佈Future完成的那個callable自己去調了這些回調。如果add_callback的時候Future已經完成,就讓add_callback自己去直接調用了這個回調,這樣效果就統一了。這其中,唯一的祕密在於,Future調用callback並不是直接調用(否則反覆觸發會導致棧溢出),而是通過前面說的延遲調用,將callable放到事件隊列裏,讓事件循環幫忙調用一下。

使用Future的好處在於可以方便地管理回調——也就只有這麼點用了。順便它可以保存一下結果,回調的時候能讀出來這個Future之前是成功了還是失敗了。

Future可以cancel,cancel實際上是爲Future設置了一個特殊的“已取消”的結果,其他跟普通的結果沒有多大區別。

Future可以串聯或並聯起來,串聯的Future使用一個callback自動觸發後一個Future,這樣這兩個Future就會依次完成,可以用來將不同的流程連接起來;並聯起來的多個Future,可以在全部完成,或者第一個完成之類的情況下觸發另一個Future,實現一些較複雜的流程控制。

Coroutine與Task

下一個問題是回調結構,說白了,我們不太喜歡callback hell——一段複雜的異步代碼需要寫非常多的callback閉包,管理狀態的時候很麻煩。怎麼解決呢?Python有一種結構叫做生成器,也就是帶有yield的函數,叫做生成器函數,它在調用的時候會返回一個生成器(generator),這個生成器可以步進——也就是說,調用一下next,就走一小段,暫停下來,返回一個值;再調用一下,又走一小段,返回一個值。這個結構就很有意思了,我們可以用它來代替普通的callback,每次callback的時候步進一小段就好了,它的結構讓它自己就可以保存之前的過程的狀態,在外部看來是異步(每次調用一個callable),在它自己看來卻是一個連續的同步過程,這樣就可以用類似於同步的寫法來寫異步過程了。

更妙的是,Python中的生成器不僅可以每次返回一個值,它還可以再接收一個值,也可以在暫停的位置直接拋出異常,這就有很豐富的流程控制體驗了。

 

我們將生成器跟Future結合起來,就可以設計一種叫做coroutine的異步流程,它是一個生成器,每次通過yield返回一個Future,然後我們爲這個Future設置一個callback,這個callback觸發的時候,我們將Future裏保存的結果發送給這個coroutine,這會讓coroutine往下再步進一步,得到下一個Future,這樣依次執行下去,我們就可以讓這個coroutine在我們的異步模型當中以一種類似於同步的方式執行下去。

要做到這個目的,我們需要有一個殼套在生成器外面,它用來自動調用Future的add_callback,做一些coroutine和事件循環之間的適配的工作,這個殼就叫做asyncio.Task,把一個coroutine放到一個Task的殼裏面,coroutine就可以在事件循環裏相對獨立地執行了。asyncio.Task自己也是一個Future的子類,這樣也可以把asyncio.Task作爲一個Future來使用,就可以等待一個任務執行結束了;區別在於,Task的結果是自動用coroutine返回值設置的,而且Task的cancel不僅會設置“已取消”的結果,還會立即在coroutine中拋出異常,嘗試使coroutine提前結束。

除了使用Task作爲Future來串聯,coroutine之間還可以級聯——在一個coroutine內,想要執行另一個coroutine,只需要將這個coroutine的yield值和返回值,以及外部發送的值全部都代理到內層coroutine裏就行了。在Python 3中有一個專門的yield from的語法用來做這件事。

從Python 3.5開始,coroutine有了一個專門的async def的寫法,將coroutine和普通的生成器區分開。async def中的await語義和yield有些區別,它更接近於yield from,因此如果await一個coroutine,實際上是前面說的級聯;如果await一個Future,Future通過__await__方法在內部yield了自己,這樣就跟直接yield一個Future沒太大區別了,由此所有的邏輯都可以通過簡單的await語法來實現。

處理polling和timer時也很簡單,方法實際上是創建一個新的Future,用polling或者timer的回調來設置Future的結果,剩下的就可以回到使用Future的方法上。

舉例

async def do_something():
    await asyncio.sleep(2)
    r = await some_other_thing()
    return r + 1

這是一個典型的coroutine函數,調用這個函數得到一個coroutine對象。這個coroutine首先等待了一個Future(asyncio.sleep返回一個定時器相關的Future),然後級聯了另一個coroutine方法,最後返回了一個結果。使用loop.run_until_complete()就可以執行這個異步過程了,也可以在其他異步過程中await這個過程,或者使用 asyncio.ensure_future()異步執行這個過程。

同步堵塞
import time

def hello():
    time.sleep(1)

def run():
    for i in range(5):
        hello()
        print('Hello World:%s' % time.time())  # 任何偉大的代碼都是從Hello World 開始的!
if __name__ == '__main__':
    run()
異步執行,類似協程互不影響,線程處理
import time
import asyncio

# 定義異步函數
async def hello():
    asyncio.sleep(1)
    print('Hello World:%s' % time.time())

def run():
    for i in range(5):
        loop.run_until_complete(hello())

loop = asyncio.get_event_loop()
if __name__ =='__main__':
    run()

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章