Python並發編程之初識異步IO框架:asyncio 上篇(九)


大家好,並發編程 進入第九篇。

通過前兩節的鋪墊(關於協程的使用),今天我們終於可以來介紹我們整個系列的重點 -- asyncio

asyncio是Python 3.4版本引入的標准庫,直接內置了對異步IO的支持。

有些同學,可能很疑惑,既然有了以生成器為基礎的協程,我們直接使用yieldyield 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

小明不是胡說八道的。有實錘。

普通函數中 不能使用 await普通函數中 不能使用 await
再來一錘。
async 中 不能使用yieldasync 中 不能使用yield

除此之外呢,還有一點很重要的。

  • yield from 后面可接 可迭代對象,也可接future對象/協程對象;
  • await 后面必須要接 future對象/協程對象

如何驗證呢?

yield from 后面可接 可迭代對象,這個前兩章已經說過了,這里不再贅述。
接下來,就只要驗證,yield fromawait都可以接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

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM