asyncio是Python3.4引入的一個(gè)標(biāo)準(zhǔn)庫(kù),直接內(nèi)置了對(duì)異步IO的支持。asyncio模塊提供了使用協(xié)程構(gòu)建并發(fā)應(yīng)用的工具。它使用一種單線程單進(jìn)程的的方式實(shí)現(xiàn)并發(fā),應(yīng)用的各個(gè)部分彼此合作, 可以顯示的切換任務(wù),一般會(huì)在程序阻塞I/O操作的時(shí)候發(fā)生上下文切換如等待讀寫(xiě)文件,或者請(qǐng)求網(wǎng)絡(luò)。同時(shí)asyncio也支持調(diào)度代碼在將來(lái)的某個(gè)特定事件運(yùn)行,從而支持一個(gè)協(xié)程等待另一個(gè)協(xié)程完成,以處理系統(tǒng)信號(hào)和識(shí)別其他一些事件。 異步并發(fā)的概念對(duì)于其他的并發(fā)模型大多數(shù)采取的都是線性的方式編寫(xiě)。并且依賴于語(yǔ)言運(yùn)行時(shí)系統(tǒng)或操作系統(tǒng)的底層線程或進(jìn)程來(lái)適當(dāng)?shù)馗淖兩舷挛?,而基于asyncio的應(yīng)用要求應(yīng)用代碼顯示的處理上下文切換。 asyncio提供的框架以事件循環(huán)(event loop)為中心,程序開(kāi)啟一個(gè)無(wú)限的循環(huán),程序會(huì)把一些函數(shù)注冊(cè)到事件循環(huán)上。當(dāng)滿足事件發(fā)生的時(shí)候,調(diào)用相應(yīng)的協(xié)程函數(shù)。 事件循環(huán)事件循環(huán)是一種處理多并發(fā)量的有效方式,在維基百科中它被描述為「一種等待程序分配事件或消息的編程架構(gòu)」,我們可以定義事件循環(huán)來(lái)簡(jiǎn)化使用輪詢方法來(lái)監(jiān)控事件,通俗的說(shuō)法就是「當(dāng)A發(fā)生時(shí),執(zhí)行B」。事件循環(huán)利用poller對(duì)象,使得程序員不用控制任務(wù)的添加、刪除和事件的控制。事件循環(huán)使用回調(diào)方法來(lái)知道事件的發(fā)生。它是asyncio提供的「中央處理設(shè)備」,支持如下操作: 注冊(cè)、執(zhí)行和取消延遲調(diào)用(超時(shí)) 創(chuàng)建可用于多種類型的通信的服務(wù)端和客戶端的Transports 啟動(dòng)進(jìn)程以及相關(guān)的和外部通信程序的Transports 將耗時(shí)函數(shù)調(diào)用委托給一個(gè)線程池 單線程(進(jìn)程)的架構(gòu)也避免的多線程(進(jìn)程)修改可變狀態(tài)的鎖的問(wèn)題。 與事件循環(huán)交互的應(yīng)用要顯示地注冊(cè)將運(yùn)行的代碼,讓事件循環(huán)在資源可用時(shí)向應(yīng)用代碼發(fā)出必要的調(diào)用。如:一個(gè)套接字再?zèng)]有更多的數(shù)據(jù)可以讀取,那么服務(wù)器會(huì)把控制全交給事件循環(huán)。 Futurefuture是一個(gè)數(shù)據(jù)結(jié)構(gòu),表示還未完成的工作結(jié)果。事件循環(huán)可以監(jiān)視Future對(duì)象是否完成。從而允許應(yīng)用的一部分等待另一部分完成一些工作。 Tasktask是Future的一個(gè)子類,它知道如何包裝和管理一個(gè)協(xié)程的執(zhí)行。任務(wù)所需的資源可用時(shí),事件循環(huán)會(huì)調(diào)度任務(wù)允許,并生成一個(gè)結(jié)果,從而可以由其他協(xié)程消費(fèi)。 異步方法使用asyncio也就意味著你需要一直寫(xiě)異步方法。 一個(gè)標(biāo)準(zhǔn)方法是這樣的: 而一個(gè)異步方法: 從外觀上看異步方法和標(biāo)準(zhǔn)方法沒(méi)什么區(qū)別只是前面多了個(gè)async。 “Async” 是“asynchronous”的簡(jiǎn)寫(xiě),為了區(qū)別于異步函數(shù),我們稱標(biāo)準(zhǔn)函數(shù)為同步函數(shù), 從用戶角度異步函數(shù)和同步函數(shù)有以下區(qū)別: 要調(diào)用異步函數(shù),必須使用await關(guān)鍵字。 因此,不要寫(xiě)regular_double(3),而是寫(xiě)await async_double(3). 不能在同步函數(shù)里使用await,否則會(huì)出錯(cuò)。 句法錯(cuò)誤: 但是在異步函數(shù)中,await是被允許的: 協(xié)程 啟動(dòng)一個(gè)協(xié)程 一般異步方法被稱之為協(xié)程(Coroutine)。asyncio事件循環(huán)可以通過(guò)多種不同的方法啟動(dòng)一個(gè)協(xié)程。一般對(duì)于入口函數(shù),最簡(jiǎn)答的方法就是使用run_until_complete,并將協(xié)程直接傳入這個(gè)方法。 輸出 這就是最簡(jiǎn)單的一個(gè)協(xié)程的例子,下面讓我們了解一下上面的代碼. 第一步首先得到一個(gè)事件循環(huán)的應(yīng)用也就是定義的對(duì)象loop??梢允褂媚J(rèn)的事件循環(huán),也可以實(shí)例化一個(gè)特定的循環(huán)類(比如uvloop),這里使用了默認(rèn)循環(huán)run_until_complete(coro)方法用這個(gè)協(xié)程啟動(dòng)循環(huán),協(xié)程返回時(shí)這個(gè)方法將停止循環(huán)。 run_until_complete的參數(shù)是一個(gè)futrue對(duì)象。當(dāng)傳入一個(gè)協(xié)程,其內(nèi)部會(huì)自動(dòng)封裝成task,其中task是Future的子類。關(guān)于task和future后面會(huì)提到。 從協(xié)程中返回值將上面的代碼,改寫(xiě)成下面代碼 run_until_complete可以獲取協(xié)程的返回值,如果沒(méi)有給定返回值,則像函數(shù)一樣,默認(rèn)返回None。 協(xié)程調(diào)用協(xié)程一個(gè)協(xié)程可以啟動(dòng)另一個(gè)協(xié)程,從而可以任務(wù)根據(jù)工作內(nèi)容,封裝到不同的協(xié)程中。我們可以在協(xié)程中使用await關(guān)鍵字,鏈?zhǔn)降恼{(diào)度協(xié)程,來(lái)形成一個(gè)協(xié)程任務(wù)流。向下面的例子一樣。 輸出 協(xié)程中調(diào)用普通函數(shù) 在協(xié)程中可以通過(guò)一些方法去調(diào)用普通的函數(shù)。可以使用的關(guān)鍵字有call_soon,call_later,call_at。 call_soon 可以通過(guò)字面意思理解調(diào)用立即返回。 loop.call_soon(callback, *args, context=None) 在下一個(gè)迭代的時(shí)間循環(huán)中立刻調(diào)用回調(diào)函數(shù),大部分的回調(diào)函數(shù)支持位置參數(shù),而不支持”關(guān)鍵字參數(shù)”,如果是想要使用關(guān)鍵字參數(shù),則推薦使用functools.aprtial對(duì)方法進(jìn)一步包裝.可選關(guān)鍵字context允許指定要運(yùn)行的回調(diào)的自定義contextvars.Context。當(dāng)沒(méi)有提供上下文時(shí)使用當(dāng)前上下文。在Python 3.7中, asyncio 協(xié)程加入了對(duì)上下文的支持。使用上下文就可以在一些場(chǎng)景下隱式地傳遞變量,比如數(shù)據(jù)庫(kù)連接session等,而不需要在所有方法調(diào)用顯示地傳遞這些變量。 下面來(lái)看一下具體的使用例子。 輸出結(jié)果 通過(guò)輸出結(jié)果我們可以發(fā)現(xiàn)我們?cè)趨f(xié)程中成功調(diào)用了一個(gè)普通函數(shù),順序的打印了1和2。 有時(shí)候我們不想立即調(diào)用一個(gè)函數(shù),此時(shí)我們就可以call_later延時(shí)去調(diào)用一個(gè)函數(shù)了。 call_later loop.call_later(delay, callback, *args, context=None) 首先簡(jiǎn)單的說(shuō)一下它的含義,就是事件循環(huán)在delay多長(zhǎng)時(shí)間之后才執(zhí)行callback函數(shù). 配合上面的call_soon讓我們看一個(gè)小例子 輸出 通過(guò)上面的輸出可以得到如下結(jié)果: 1.call_soon會(huì)在call_later之前執(zhí)行,和它的位置在哪無(wú)關(guān) 2.call_later的第一個(gè)參數(shù)越小,越先執(zhí)行。 call_atloop.call_at(when, callback, *args, context=None) call_at第一個(gè)參數(shù)的含義代表的是一個(gè)單調(diào)時(shí)間,它和我們平時(shí)說(shuō)的系統(tǒng)時(shí)間有點(diǎn)差異, 這里的時(shí)間指的是事件循環(huán)內(nèi)部時(shí)間,可以通過(guò)loop.time獲取,然后可以在此基礎(chǔ)上進(jìn)行操作。后面的參數(shù)和前面的兩個(gè)方法一樣。實(shí)際上call_later內(nèi)部就是調(diào)用的call_at。 輸出 因?yàn)閏all_later內(nèi)部實(shí)現(xiàn)就是通過(guò)call_at所以這里就不多說(shuō)了。 Future獲取Futrue里的結(jié)果 future表示還沒(méi)有完成的工作結(jié)果。事件循環(huán)可以通過(guò)監(jiān)視一個(gè)future對(duì)象的狀態(tài)來(lái)指示它已經(jīng)完成。future對(duì)象有幾個(gè)狀態(tài): Pending Running Done Cancelled 創(chuàng)建future的時(shí)候,task為pending,事件循環(huán)調(diào)用執(zhí)行的時(shí)候當(dāng)然就是running,調(diào)用完畢自然就是done,如果需要停止事件循環(huán),就需要先把task取消,狀態(tài)為cancel。 輸出 可以通過(guò)輸出結(jié)果發(fā)現(xiàn),調(diào)用set_result之后future對(duì)象的狀態(tài)由pending變?yōu)閒inished,F(xiàn)uture的實(shí)例all_done會(huì)保留提供給方法的結(jié)果,可以在后續(xù)使用。 Future對(duì)象使用awaitfuture和協(xié)程一樣可以使用await關(guān)鍵字獲取其結(jié)果。 ![]() Future回調(diào) Future 在完成的時(shí)候可以執(zhí)行一些回調(diào)函數(shù),回調(diào)函數(shù)按注冊(cè)時(shí)的順序進(jìn)行調(diào)用: ![]() 通過(guò)add_done_callback方法給funtrue任務(wù)添加回調(diào)函數(shù),當(dāng)funture執(zhí)行完成的時(shí)候,就會(huì)調(diào)用回調(diào)函數(shù)。并通過(guò)參數(shù)future獲取協(xié)程執(zhí)行的結(jié)果。 到此為止,我們就學(xué)會(huì)了如何在協(xié)程中調(diào)用一個(gè)普通函數(shù)并獲取其結(jié)果。 并發(fā)的執(zhí)行任務(wù)任務(wù)(Task)是與事件循環(huán)交互的主要途徑之一。任務(wù)可以包裝協(xié)程,可以跟蹤協(xié)程何時(shí)完成。任務(wù)是Future的子類,所以使用方法和future一樣。協(xié)程可以等待任務(wù),每個(gè)任務(wù)都有一個(gè)結(jié)果,在它完成之后可以獲取這個(gè)結(jié)果。 因?yàn)閰f(xié)程是沒(méi)有狀態(tài)的,我們通過(guò)使用create_task方法可以將協(xié)程包裝成有狀態(tài)的任務(wù)。還可以在任務(wù)運(yùn)行的過(guò)程中取消任務(wù)。 ![]() 輸出 如果把上面的task.cancel注釋了我們可以得到正常情況下的結(jié)果,如下。 另外出了使用loop.create_task將協(xié)程包裝為任務(wù)外還可以使用asyncio.ensure_future(coroutine)建一個(gè)task。在python3.7中可以使用asyncio.create_task創(chuàng)建任務(wù)。 組合協(xié)程一系列的協(xié)程可以通過(guò)await鏈?zhǔn)降恼{(diào)用,但是有的時(shí)候我們需要在一個(gè)協(xié)程里等待多個(gè)協(xié)程,比如我們?cè)谝粋€(gè)協(xié)程里等待1000個(gè)異步網(wǎng)絡(luò)請(qǐng)求,對(duì)于訪問(wèn)次序有沒(méi)有要求的時(shí)候,就可以使用另外的關(guān)鍵字wait或gather來(lái)解決了。wait可以暫停一個(gè)協(xié)程,直到后臺(tái)操作完成。 等待多個(gè)協(xié)程 Task的使用 ![]() 輸出 ![]() 可以發(fā)現(xiàn)我們的結(jié)果并沒(méi)有按照數(shù)字的順序顯示,在內(nèi)部wait使用一個(gè)set保存它創(chuàng)建的Task實(shí)例。因?yàn)閟et是無(wú)序的所以這也就是我們的任務(wù)不是順序執(zhí)行的原因。wait的返回值是一個(gè)元組,包括兩個(gè)集合,分別表示已完成和未完成的任務(wù)。wait第二個(gè)參數(shù)為一個(gè)超時(shí)值 達(dá)到這個(gè)超時(shí)時(shí)間后,未完成的任務(wù)狀態(tài)變?yōu)閜ending,當(dāng)程序退出時(shí)還有任務(wù)沒(méi)有完成此時(shí)就會(huì)看到如下的錯(cuò)誤提示。 ![]() 此時(shí)我們可以通過(guò)迭代調(diào)用cancel方法取消任務(wù)。也就是這段代碼 gather的使用 gather的作用和wait類似不同的是。 1.gather任務(wù)無(wú)法取消。 2.返回值是一個(gè)結(jié)果列表 3.可以按照傳入?yún)?shù)的順序,順序輸出。 我們將上面的代碼改為gather的方式 ![]() 輸出 gather通常被用來(lái)階段性的一個(gè)操作,做完第一步才能做第二步,比如下面這樣 ![]() 輸出 ![]() 可以通過(guò)上面結(jié)果得到如下結(jié)論: 1.step1和step2是并行運(yùn)行的。 2.gather會(huì)等待最耗時(shí)的那個(gè)完成之后才返回結(jié)果,耗時(shí)總時(shí)間取決于其中任務(wù)最長(zhǎng)時(shí)間的那個(gè)。 任務(wù)完成時(shí)進(jìn)行處理as_complete是一個(gè)生成器,會(huì)管理指定的一個(gè)任務(wù)列表,并生成他們的結(jié)果。每個(gè)協(xié)程結(jié)束運(yùn)行時(shí)一次生成一個(gè)結(jié)果。與wait一樣,as_complete不能保證順序,不過(guò)執(zhí)行其他動(dòng)作之前沒(méi)有必要等待所以后臺(tái)操作完成。 ![]() 輸出 ![]() 可以發(fā)現(xiàn)結(jié)果逐個(gè)輸出。 到此為止第一部分就結(jié)束了,對(duì)于asyncio入門(mén)級(jí)學(xué)習(xí)來(lái)說(shuō)這些內(nèi)容就夠了。如果想繼續(xù)跟進(jìn)asyncio的內(nèi)容,敬請(qǐng)期待后面的內(nèi)容。 |
|