asyncio模塊做爲一個臨時的庫,在Python 3.4版本中加入。這意味着,asyncio模塊可能作不到向後兼容甚至在後續的Python版本中被刪除。根據Python官方文檔,asyncio經過coroutines、sockets和其它資源上的多路複用IO訪問、運行網絡客戶端和服務端以及其它相關的原始服務等提供了一種單線程併發應用的架構。本文並不能覆蓋全部關於asyncio模塊的技術點,可是你能夠學到如何去使用這個模塊,以及爲何它是有用的。python
若是你在一些較老的Python版本中須要一些相似於asyncio模塊的技術,你能夠看Twisted或者gevent。編程
asyncio模塊提供了一種關於事件循環的框架。事件循環就是等待一些任務發生,而後執行相應的事件。它也會處理例如IO操做或者系統事件。asyncio實際中有好幾種循環實現方式。模塊默認使用的方式是其所運行的操做系統上最有效的方式。若是你願意,你也能夠顯式地選擇其它事件循環方式。一個事件循環就是當事件A發生時,函數B共同起做用。安全
設想這樣一個場景,服務器等待用戶訪問並請求一些資源,例如網頁。若是這個網站不是很是知名的網站,這個服務器將會在很長的時間內處於空閒狀態。可是,一旦某個時間用戶點擊了這個網站,服務器就須要做出響應。這個響應就是事件處理。當一個用戶下載網頁,服務器將會去檢查並調用一個或者多個事件句柄。一旦這些事件句柄完成相應的處理,它們須要將控制交回給事件循環。爲了在Python中完成這個任務,asyncio使用協程。服務器
協程是一個特殊的函數,能夠將控制交回給它的調用函數,可是並不丟失它的狀態。協程是一個消費者函數,而且是生成器的擴展。協程相比線程最大的優點就是執行協程時不須要佔用太多內存。你須要注意的是,當你調用一個協程函數,它並無真正執行。相反,它將會返回一個協程對象,你能夠將這個協程對象傳遞給事件循環,而後能夠當即或者稍後執行它。網絡
當你在使用asyncio模塊時,另外一個你可能會執行的是future。future就是一個能夠表示尚未結束的任務結果的對象。你的事件循環能夠觀察future對象並等待它們結束。當一個future結束時,它被設置爲已完成。asyncio模塊也支持鎖和信號。架構
本文最後一部分,我將會提到Task。Task是協程的一個框架,是Future的一個子類。你能夠在事件循環中對Task進行調度。併發
async和await是Python 3.5中新添加的關鍵詞,用來定義一個原生的協程,以便於和基於協程的生成器相區別。若是你想了解更多關於async和await的知識,你能夠去閱讀PEP 492。框架
在Python 3.4中,你能夠按照以下方式建立一個協程,異步
import asyncio @asyncio.coroutine def my_foo(): yield from func()
這個裝飾器在Python 3.5中依然有效,可是模塊的類型有所更新,協程函數能夠告訴你正在交互的是否是一個原生的協程。從Python 3.5開始,你可使用async def這種語法來定義一個協程函數,因此上述函數能夠按照以下方式定義,socket
import asyncio async def my_coro(): await func()
當你以這種方式定義一個協程函數,你不能在函數內部使用yield。取而代之,你必須使用return或者await語句,用於將返回值返回給調用者。你須要注意的是,關鍵字await只能在async def函數中使用。
關鍵字async和await能夠認爲是異步編程中的接口。asyncio模塊就是一個能夠將async/await用於異步編程的框架。實際上,有一個叫作curio的項目證明了這個概念,那就是它單獨實現了在後臺使用async/await的事件循環。
儘管上述的描述可讓你得到不少關於協程如何工做的背景知識,有時候,你僅僅想看到一些示例,這樣你就能夠切身感覺到它的語法形式,以及如何將這些代碼組合在一塊兒。考慮到這一點,讓咱們以一個簡單的示例開始把。
一個很是常見的任務就是你想完整的下載一個文件,這個文件可能來源於內部資源或者互聯網。固然你想要下載的文件可能不止一個。讓咱們建立兩個協程來完成這個任務。
import asyncio import os import urllib.request async def download_coroutine(url): request = urllib.request.urlopen(url) filename = os.path.basename(url) with open(filename,"wb") as file_handle: while True: chunk = request.read(1024) if not chunk: break file_handle.write(chunk) msg = "Finished downloading {filename}".format(filename = filename) return msg async def main(urls): coroutines = [download_coroutine(url) for url in urls] completed,pending = awit asyncio.wait(coroutines) for item in completed: print(item.result()) if __name__ == "__main__": urls = ["http://www.irs.gov/pub/irs-pdf/f1040.pdf", "http://www.irs.gov/pub/irs-pdf/f1040a.pdf", "http://www.irs.gov/pub/irs-pdf/f1040ez.pdf", "http://www.irs.gov/pub/irs-pdf/f1040es.pdf", "http://www.irs.gov/pub/irs-pdf/f1040sb.pdf"] event_loop = asyncio.get_event_loop() try: event_loop.run_until_complete(main(urls)) finally: event_loop.close()
這段代碼中,咱們引入了咱們須要的模塊,而後經過async語法建立了第一個協程。這個協程叫作download_coroutine,它使用Python的urllib模塊下載傳遞給它的任何URL地址。當它完成任務時,它將會返回一條相應的信息。
另外一個協程就是咱們的主協程。它基本上就是獲取一個包含一個或者多個URL地址的列表,而後將它們加入隊列。咱們使用asyncio的wait函數用於等待協程的結束。固然,爲了啓動這些協程,它們須要被加入到事件循環中。咱們在代碼段中最後的地方作了這個處理,咱們先獲取一個事件循環,而後調用它的run_until_complete的方法。你將會注意到,咱們將主協程傳入事件循環中。這個會先運行主協程,主協程將第二個協程加入到隊列中,並讓它們運行。這就是有名的鏈協程。
你也能夠經過異步事件循環來調度調用常規函數。咱們看的第一個方法是call_soon。方法call_soon基本上就是儘量的調用你的回調或者事件句柄。它的工做機制相似於先進先出隊列,因此若是一些回調須要一段時間來處理任務,其它的回調就會相應的延遲,直到先前的回調結束。讓咱們來看一個示例。
import asyncio import functools def event_handler(loop,stop = False): print("Event handler called") if stop: print("Stopping the loop") loop.stop() if __name__ == "__main__": loop = asyncio.get_event_loop() try: loop.call_soon(functools.partial(event_handler,loop)) print("Starting event loop") loop.call_soon(functools.partial(event_handler,loop,stop = True)) loop.run_forever() finally: print("closing event loop") loop.close()
因爲asyncio的函數不接受關鍵字,可是若是咱們須要將關鍵字傳入事件句柄中,那麼咱們就須要使用functools模塊了。不管什麼時候被調用,咱們定義的常規函數將會在標準輸出上打印一些文字信息。若是你偶然將這個函數的stop變量設置爲True,它將會中止事件循環。
第一次咱們調用它時,咱們沒有中止事件循環。第二次咱們調用它時,咱們中止了事件循環。咱們中止事件循環的緣由是咱們將它放入run_forever,這個將時間循環設置爲無限循環。一旦循環中止,咱們就能夠將它關閉。若是你運行這段代碼,你獲得的輸出以下所示,
Starting event loop Event handler called Event handler called Stopping the loop closing event loop
還有一個相關的函數是call_soon_threadsafe,顧名思義,它與call_soon的工做機制類似,可是它是線程安全的。
若是你想延遲一段時間再調用,你可使用call_later函數。在這個示例中,咱們能夠將call_soon函數按照以下方式修改,
loop.call_later(1,event_handler,loop)
這個將會延遲調用咱們的事件句柄1秒鐘,而後纔會去調用它,並將循環做爲第一個參數傳入。
若是你想在將來一個指定的時間調度,你須要獲取循環的時間,而不是計算機的時間,你能夠按照以下方式操做,
current_time = loop.time()
一旦你這樣作,你可使用call_at函數,而後將你想調用事件句柄的時間傳遞給它。讓咱們來看看咱們想在5分鐘以後調用咱們的事件句柄,下面就是你如何操做的,
loop.call_at(current_time + 300,event_handler,loop)
在這個示例中,咱們使用咱們獲取的當前時間,而後加上300秒鐘或者5分鐘。經過這個操做,咱們延遲調用事件循環5分鐘。
Task是Future的一個子類,也是協程的一個框架。Task可讓你記錄到任務結束處理的時間。因爲任務是Future類型,其它的協程能夠等待一個任務,當任務處理完畢時你也能夠獲取到它的結果。讓咱們看一個簡單的示例。
import asyncio import time async def my_task(seconds): print("This task is take {} seconds to cpmplete".format(seconds)) time.sleep(seconds) return "task finished" if __name__ == "__main__": my_event_loop = asyncio.get_event_loop() try: print("task creation started") task_obj = my_event_loop.create_task(my_task(seconds = 2)) my_event_loop.run_until_complete(task_obj) finally: my_event_loop.close() print("The task's result was :{}".format(task_obj.result()))
在這裏,咱們建立一個異步函數,它接受秒數,也是它將會運行的時間。這個模仿了一個長時間運行的任務。而後咱們建立了咱們的事件循環,而且經過事件循環對象的create_task函數建立了一個任務對象。函數create_task接受咱們想要轉換爲任務的函數。而後咱們運行事件循環,直到任務完成。在最後,一旦任務結束,咱們就得到任務的結果。
經過任務的cancel方法,任務也能夠很容易被取消。當你想結束一個任務,調用它就能夠了。當一個任務在等待另外一個操做時被取消,這個任務將會報出CancelError錯誤。
到這裏,你應該已經瞭解如何利用asyncio庫進行工做了。asyncio庫是很是強大的,它容許你去作不少酷而且有意思的任務。你能夠查看http://asyncio.org/,該網站包含了不少使用asyncio的項目,能夠獲取到不少關於如何使用asyncio庫的靈感。固然,Python官方文檔也是一個很好的開始asyncio之旅的地方。