大家好,並發編程
進入第九篇。
通過前兩節的鋪墊(關於協程的使用),今天我們終於可以來介紹我們整個系列的重點 -- asyncio
。
asyncio
是Python 3.4版本引入的標准庫,直接內置了對異步IO的支持。
有些同學,可能很疑惑,既然有了以生成器為基礎的協程,我們直接使用yield
和 yield from
不就可以手動實現對IO的調度了嗎? 為何Python吃飽了沒事干,老重復造輪子。
這個問題很好回答,就跟為什么會有Django
,為什么會有Scrapy
,是一個道理。
他們都是框架,將很多很重復性高,復雜度高的工作,提前給你做好,這樣你就可以專注於業務代碼的研發。
跟着小明學完了協程的那些個難點,你是不是也發現了,協程的知識點我已經掌握了,但是我還是不知道怎么用,如何使用,都說它可以實現並發,但是我還是不知道如何入手?
那是因為,我們現在還缺少一個成熟的框架,幫助你完成那些復雜的動作。這個時候,ayncio
就這么應運而生了。
. 本文目錄
- 如何定義/創建協程
- asyncio的幾個概念
- 學習協程是如何工作的
- await與yield對比
- 綁定回調函數
. 如何定義/創建協程
還記得在前兩章節的時候,我們創建了生成器,是如何去檢驗我們創建的是不是生成器對象嗎?
我們是借助了isinstance()
函數,來判斷是否是collections.abc
里的Generator
類的子類實現的。
同樣的方法,我們也可以用在這里。
只要在一個函數前面加上 async
關鍵字,這個函數對象是一個協程,通過isinstance
函數,它確實是Coroutine
類型。
from collections.abc import Coroutine
async def hello(name):
print('Hello,', name)
if __name__ == '__main__':
# 生成協程對象,並不會運行函數內的代碼
coroutine = hello("World")
# 檢查是否是協程 Coroutine 類型
print(isinstance(coroutine, Coroutine)) # True
前兩節,我們說,生成器是協程的基礎,那我們是不是有辦法,將一個生成器,直接變成協程使用呢。答案是有的。
import asyncio
from collections.abc import Generator, Coroutine
'''
只要在一個生成器函數頭部用上 @asyncio.coroutine 裝飾器
就能將這個函數對象,【標記】為協程對象。注意這里是【標記】,划重點。
實際上,它的本質還是一個生成器。
標記后,它實際上已經可以當成協程使用。后面會介紹。
'''
@asyncio.coroutine
def hello():
# 異步調用asyncio.sleep(1):
yield from asyncio.sleep(1)
if __name__ == '__main__':
coroutine = hello()
print(isinstance(coroutine, Generator)) # True
print(isinstance(coroutine, Coroutine)) # False
. asyncio的幾個概念
在了解asyncio
的使用方法前,首先有必要先介紹一下,這幾個貫穿始終的概念。
event_loop 事件循環
:程序開啟一個無限的循環,程序員會把一些函數(協程)注冊到事件循環上。當滿足事件發生的時候,調用相應的協程函數。coroutine 協程
:協程對象,指一個使用async關鍵字定義的函數,它的調用不會立即執行函數,而是會返回一個協程對象。協程對象需要注冊到事件循環,由事件循環調用。future 對象
: 代表將來執行或沒有執行的任務的結果。它和task上沒有本質的區別task 任務
:一個協程對象就是一個原生可以掛起的函數,任務則是對協程進一步封裝,其中包含任務的各種狀態。Task 對象是 Future 的子類,它將 coroutine 和 Future 聯系在一起,將 coroutine 封裝成一個 Future 對象。async/await 關鍵字
:python3.5 用於定義協程的關鍵字,async定義一個協程,await用於掛起阻塞的異步調用接口。其作用在一定程度上類似於yield。
這幾個概念,干看可能很難以理解,沒事,往下看實例,然后再回來,我相信你一定能夠理解。
. 學習協程是如何工作的
協程完整的工作流程是這樣的
- 定義/創建協程對象
- 將協程轉為task任務
- 定義事件循環對象容器
- 將task任務扔進事件循環對象中觸發
光說不練假把戲,一起來看下
import asyncio
async def hello(name):
print('Hello,', name)
# 定義協程對象
coroutine = hello("World")
# 定義事件循環對象容器
loop = asyncio.get_event_loop()
# task = asyncio.ensure_future(coroutine)
# 將協程轉為task任務
task = loop.create_task(coroutine)
# 將task任務扔進事件循環對象中並觸發
loop.run_until_complete(task)
輸出結果,當然顯而易見
Hello, World
. await與yield對比
前面我們說,await
用於掛起阻塞的異步調用接口。其作用在一定程度上
類似於yield。
注意這里是,一定程度上,意思是效果上一樣(都能實現暫停的效果),但是功能上卻不兼容。就是你不能在生成器中使用await
,也不能在async 定義的協程中使用yield
。
小明不是胡說八道的。有實錘。

再來一錘。

除此之外呢,還有一點很重要的。
yield from
后面可接可迭代對象
,也可接future對象
/協程對象;await
后面必須要接future對象
/協程對象
如何驗證呢?
yield from
后面可接 可迭代對象
,這個前兩章已經說過了,這里不再贅述。
接下來,就只要驗證,yield from
和await
都可以接future對象
/協程對象
就可以了。
驗證之前呢,要先介紹一下這個函數:asyncio.sleep(n)
,這貨是asyncio自帶的工具函數,他可以模擬IO阻塞,他返回的是一個協程對象。
func = asyncio.sleep(2)
print(isinstance(func, Future)) # False
print(isinstance(func, Coroutine)) # True
還有,要學習如何創建Future對象
,不然怎么驗證。
前面概念里說過,Task是Future的子類,這么說,我們只要創建一個task對象即可。
import asyncio
from asyncio.futures import Future
async def hello(name):
await asyncio.sleep(2)
print('Hello, ', name)
coroutine = hello("World")
# 將協程轉為task對象
task = asyncio.ensure_future(coroutine)
print(isinstance(task, Future)) # True
好了,接下來,開始驗證。

. 綁定回調函數
異步IO的實現原理,就是在IO高的地方掛起,等IO結束后,再繼續執行。在絕大部分時候,我們后續的代碼的執行是需要依賴IO的返回值的,這就要用到回調了。
回調的實現,有兩種,一種是絕大部分程序員喜歡的,利用的同步編程實現的回調。
這就要求我們要能夠有辦法取得協程的await的返回值。
import asyncio
import time
async def _sleep(x):
time.sleep(2)
return '暫停了{}秒!'.format(x)
coroutine = _sleep(2)
loop = asyncio.get_event_loop()
task = asyncio.ensure_future(coroutine)
loop.run_until_complete(task)
# task.result() 可以取得返回結果
print('返回結果:{}'.format(task.result()))
輸出
返回結果:暫停了2秒!
還有一種是通過asyncio自帶的添加回調函數功能來實現。
import time
import asyncio
async def _sleep(x):
time.sleep(2)
return '暫停了{}秒!'.format(x)
def callback(future):
print('這里是回調函數,獲取返回結果是:', future.result())
coroutine = _sleep(2)
loop = asyncio.get_event_loop()
task = asyncio.ensure_future(coroutine)
# 添加回調函數
task.add_done_callback(callback)
loop.run_until_complete(task)
輸出
這里是回調函數,獲取返回結果是: 暫停了2秒!
emmm,和上面的結果是一樣的。nice
