1. 程式人生 > 程式設計 >python 非同步async庫的使用說明

python 非同步async庫的使用說明

在學習asyncio之前,先理清楚同步/非同步的概念:

同步是指完成事務的邏輯,先執行第一個事務,如果阻塞了,會一直等待,直到這個事務完成,再執行第二個事務,順序執行

非同步是和同步相對的,非同步是指在處理呼叫這個事務的之後,不會等待這個事務的處理結果,直接處理第二個事務去了,通過狀態、通知、回撥來通知呼叫者處理結果

asyncio函式:

非同步IO採用訊息迴圈的模式,重複“讀取訊息—處理訊息”的過程,也就是說非同步IO模型”需要一個訊息迴圈,在訊息迴圈中,主執行緒不斷地重複“讀取訊息-處理訊息”這一過程。

event_loop 事件迴圈:程式開啟一個無限的迴圈,程式設計師會把一些函式註冊到事件迴圈上。當滿足事件發生的時候,呼叫相應的協程函式。

coroutine 協程:協程物件,指一個使用async關鍵字定義的函式,它的呼叫不會立即執行函式,而是會返回一個協程物件。協程物件需要註冊到事件迴圈,由事件迴圈呼叫。

task 任務:一個協程物件就是一個原生可以掛起的函式,任務則是對協程進一步封裝,其中包含任務的各種狀態。

async/await 關鍵字: 用於定義協程的關鍵字,async定義一個協程,await用於掛起阻塞的非同步呼叫介面。

一、asyncio

下面通過舉例來對比同步程式碼和非同步程式碼編寫方面的差異,其次看下兩者效能上的差距,使用asyncio.sleep(1)模擬耗時1秒的io操作。

同步程式碼:

import time

def hello():
  time.sleep(1)

def run():
  for i in range(5):
    hello()
    print('Hello World:%s' % time.time()) 
if __name__ == '__main__':
  run()

Hello World:1536842494.2786784
Hello World:1536842495.2796268
Hello World:1536842496.2802596
Hello World:1536842497.2804587
Hello World:1536842498.2812462

非同步程式碼:

import time
import asyncio

# 定義非同步函式
async def hello():
  print('Hello World:%s' % time.time())
  #必須使用await,不能使用yield from;如果是使用yield from ,需要採用@asyncio.coroutine相對應
  await asyncio.sleep(1)  
  print('Hello wow World:%s' % time.time())

def run():
  tasks = []
  for i in range(5):
    tasks.append(hello())
  loop.run_until_complete(asyncio.wait(tasks))

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

Hello World:1536855050.1950748
Hello World:1536855050.1950748
Hello World:1536855050.1950748
Hello World:1536855050.1960726
Hello World:1536855050.1960726
(暫停約1秒)
Hello wow World:1536855051.1993241
Hello wow World:1536855051.1993241
Hello wow World:1536855051.1993241
Hello wow World:1536855051.1993241
Hello wow World:1536855051.1993241

async def 用來定義非同步函式,其內部有非同步操作。每個執行緒有一個事件迴圈,主執行緒呼叫asyncio.get_event_loop()時會建立事件迴圈,把非同步的任務丟給這個迴圈的run_until_complete()方法,事件迴圈會安排協同程式的執行。

上述程式中,hello()會首先打印出Hello world!,然後,yield from語法可以讓我們方便地呼叫另一個generator。

由於await asyncio.sleep(1)也是一個coroutine,所以執行緒不會等待asyncio.sleep(1),而是直接中斷並執行下一個訊息迴圈。

當asyncio.sleep(1)返回時,執行緒就可以從yield from拿到返回值(此處是None),然後接著執行下一行語句。

把asyncio.sleep(1)看成是一個耗時1秒的IO操作,在此期間,主執行緒並未等待,而是去執行EventLoop中其他可以執行的coroutine了,因此可以實現併發執行。

asyncio操作的總結:

async def hello(): 定義async非同步函式,中間可以新增await async.sleep(N) 來設定中斷並執行下一個迴圈訊息

tasks = [] 任務則是對協程進一步封裝,其中包含任務的各種狀態。即多個coroutine函式可以封裝成一組Task然後併發執行

loop = asyncio.get_event_loop() #獲取“事件迴圈”物件

loop.run_until_complete(asyncio.wait(tasks)) #通過事件迴圈,去呼叫協程函式

loop.close() 結束時間迴圈

二、aiohttp   

如果需要併發http請求,通常是用requests,但requests是同步的庫,如果想非同步的話需要引入aiohttp。

這裡引入一個類,from aiohttp import ClientSession,首先要建立一個session物件,然後用session物件去開啟網頁。

session可以進行多項操作,比如post,get,put,head等。

基本用法:

async with ClientSession() as session:

async with session.get(url) as response:

aiohttp非同步實現的例子:

import asyncio
from aiohttp import ClientSession

tasks = []
url = "https://www.baidu.com/{}"
async def hello(url):
  async with ClientSession() as session:
    async with session.get(url) as response:
      response = await response.read()
      print(response)

if __name__ == '__main__':
  loop = asyncio.get_event_loop()
  loop.run_until_complete(hello(url))

首先async def 關鍵字定義了這是個非同步函式,await 關鍵字加在需要等待的操作前面,response.read()等待request響應,是個耗IO操作。然後使用ClientSession類發起http請求。

多連結非同步訪問

如果我們需要請求多個URL該怎麼辦呢,同步的做法訪問多個URL只需要加個for迴圈就可以了。但非同步的實現方式並沒那麼容易,在之前的基礎上需要將hello()包裝在asyncio的Future物件中,然後將Future物件列表作為任務傳遞給事件迴圈。

import time
import asyncio
from aiohttp import ClientSession

tasks = []
url = "https://www.baidu.com/{}"
async def hello(url):
  async with ClientSession() as session:
    async with session.get(url) as response:
      response = await response.read()
      print('Hello World:%s' % time.time())

def run():
  for i in range(5):
    task = asyncio.ensure_future(hello(url.format(i)))
    tasks.append(task)


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

Hello World:1536843566.064149
Hello World:1536843566.070586
Hello World:1536843566.0769563
Hello World:1536843566.0779328
Hello World:1536843566.0799286

·收集http響應

好了,上面介紹了訪問不同連結的非同步實現方式,但是我們只是發出了請求,如果要把響應一一收集到一個列表中,最後儲存到本地或者打印出來要怎麼實現呢,可通過asyncio.gather(*tasks)將響應全部收集起來

import time
import asyncio
from aiohttp import ClientSession

tasks = []
url = "https://www.baidu.com/{}"
async def hello(url):
  async with ClientSession() as session:
    async with session.get(url) as response:
#      print(response)
      print('Hello World:%s' % time.time())
      return await response.read()

def run():
  for i in range(5):
    task = asyncio.ensure_future(hello(url.format(i)))
    tasks.append(task)
  result = loop.run_until_complete(asyncio.gather(*tasks))
  print(result)

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

Hello World:1536843488.678779
Hello World:1536843488.6797836
Hello World:1536843488.6867576
Hello World:1536843488.6877556
Hello World:1536843488.6877556

以上這篇python 非同步async庫的使用說明就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支援我們。