異步編程 - asyncio

異步編程

一、事件循環

我們可以理解爲就是一個 循環 並去檢測並執行一些代碼。

# 僞代碼


任務列表 = [ {'任務1':可執行},{'任務2':IO阻塞},{"任務3":"已完成"}] 


while True:

    可執行任務列表 = [] 去任務列表檢測所有任務, 拿到可執行任務 
    已完成任務列表 = [] 去任務列表檢測所有任務,已完成的任務返回
    
    for 就緒任務 in 可執行任務列表:
        執行已就緒任務
    for 已完成任務 in 已完成任務列表:
        任務列表中 移除已完成任務
    if 任務list 中的任務都已經完成 結束循環
        pass

事件循環 我們可以看這段僞代碼 去理解 就夠了。

# 去生成一個任務循環
loop = asyncio.get_event_loop()
# 將任務放到 "任務list"
loop.run_until_complete(asyncio.wait(tasks))

二、快速上手

協程函數、定義函數的時候async def 函數名
協程對象、協程函數() 得到協程對象 但是內部代碼把 不會執行

async def func():
    pass
ret = func()

注意⚠️ 執行協程函數創建協程對象,函數內部不會執行

如果想要運行協程函數內部代碼 ,必須要交給事件循環

import asyncio
async def func(): 
    print("我執行了")
    
ret = func()

# loop = asyncio.get_event_loop()
# loop.run_until_complete(ret)

# python3.5 之後 可以這樣寫

asyncio.run( ret ) 

三、await關鍵字

await 字面意識等待的意識 await 後面一定要跟可等待的對象(協程對象 ,Future ,Task對象)>> io 等待

示例1:

import asyncio

async def func():
    print("進入程序")
    response = await  asyncio.sleep(2)
    print("程序結束",response)

ret = func()

asyncio.run(ret)

示例2:

import asyncio

async def func():
    print("開始")
    response = await  asyncio.sleep(2)
    print("結束",response)



async def func1():

    print("進入程序")

    # 遇到 IO 操作掛起當前協程任務 ,等IO操作 完成之後再繼續往下執行。當前協程掛起,事件循環可去執行其他協程任務

    await func()

    print("程序結束")

ret = func1()

asyncio.run(ret)

await 就是等待對象的值得到結果之後在往下走

注意⚠️: 有的人會感覺沒什麼有 感覺就是同步執行 一直在等

四、task對象

官方定義

Tasks are used to schedule coroutines concurrently.
When a coroutine is wrapped into a Task with functions likeasyncio.create_task() the coroutine is automatically scheduled to run soon。

Tasks用於併發調度協程,通過asyncio.create_task(協程對象)的方式創建Task對象,這樣可以讓協程加入事件循環中等待被調度執行。除了使用 asyncio.create_task() 函數以外,還可以用低層級的 loop.create_task()ensure_future() 函數。不建議手動實例化 Task 對象。

本質上是將協程對象封裝成task對象,並將協程立即加入事件循環,同時追蹤協程的狀態。

注意:asyncio.create_task() 函數在 Python 3.7 中被加入。在 Python 3.7 之前,可以改用低層級的 asyncio.ensure_future() 函數。

import asyncio


async def func():
    print(1)
    await asyncio.sleep(2)
    print(2)
    return "返回值"


async def main():
    print("main開始")

    # 創建協程,將協程封裝到一個Task對象中並立即添加到事件循環的任務列表中,等待事件循環去執行(默認是就緒狀態)。
    task1 = asyncio.create_task(func())

    # 創建協程,將協程封裝到一個Task對象中並立即添加到事件循環的任務列表中,等待事件循環去執行(默認是就緒狀態)。
    task2 = asyncio.create_task(func())

    print("main結束")

    # 當執行某協程遇到IO操作時,會自動化切換執行其他任務。
    # 此處的await是等待相對應的協程全都執行完畢並獲取結果
    ret1 = await task1
    ret2 = await task2
    print(ret1, ret2)


asyncio.run(main())

示例2:

import asyncio


async def func():
    print(1)
    await asyncio.sleep(2)
    print(2)
    return "返回值"


async def main():
    print("main開始")

    # 創建協程,將協程封裝到Task對象中並添加到事件循環的任務列表中,等待事件循環去執行(默認是就緒狀態)。
    # 在調用
    task_list = [
        asyncio.create_task(func(), name="n1"),
        asyncio.create_task(func(), name="n2")
    ]

    print("main結束")

    # 當執行某協程遇到IO操作時,會自動化切換執行其他任務。
    # 此處的await是等待所有協程執行完畢,並將所有協程的返回值保存到done
    # 如果設置了timeout值,則意味着此處最多等待的秒,完成的協程返回值寫入到done中,未完成則寫到pending中。
    done, pending = await asyncio.wait(task_list, timeout=None)
    print(done, pending)


asyncio.run(main())

注意:asyncio.wait 源碼內部會對列表中的每個協程執行ensure_future從而封裝爲Task對象,所以在和wait配合使用時task_list的值爲[func(),func()] 也是可以的。

示例:

import asyncio


async def func():
    print("執行協程函數內部代碼")

    # 遇到IO操作掛起當前協程(任務),等IO操作完成之後再繼續往下執行。當前協程掛起時,事件循環可以去執行其他協程(任務)。
    response = await asyncio.sleep(2)

    print("IO請求結束,結果爲:", response)
    return "返回值"


coroutine_list = [func(), func()]

# 錯誤:coroutine_list = [ asyncio.create_task(func()), asyncio.create_task(func()) ]
# 此處不能直接 asyncio.create_task,因爲將Task立即加入到事件循環的任務列表,
# 但此時事件循環還未創建,所以會報錯。


# 使用asyncio.wait將列表封裝爲一個協程,並調用asyncio.run實現執行兩個協程
# asyncio.wait內部會對列表中的每個協程執行ensure_future,封裝爲Task對象。
done,pending = asyncio.run( asyncio.wait(coroutine_list) )

for ret in done:
    print(ret.result())

結果:

執行協程函數內部代碼
執行協程函數內部代碼
IO請求結束,結果爲: None
IO請求結束,結果爲: None
返回值
返回值

五、 asyncio.Future對象

A Futureis a special low-level awaitable object that represents an eventual result of an asynchronous operation.

asyncio中的Future對象是一個相對更偏向底層的可對象,通常我們不會直接用到這個對象,而是直接使用Task對象來完成任務的並和狀態的追蹤。( Task 是 Futrue的子類 )

Future爲我們提供了異步編程中的 最終結果 的處理(Task類也具備狀態處理的功能)。
示例:

import asyncio

async def main():
    # 獲取當前事件循環
    loop = asyncio.get_running_loop()

    # # 創建一個任務(Future對象),這個任務什麼都不幹。
    fut = loop.create_future()

    # 等待任務最終結果(Future對象),沒有結果則會一直等下去。
    await fut

asyncio.run(main())

示例2:

import asyncio

async  def func(fut):
    await asyncio.sleep(2)
    fut.set_result("任務")

async def func1():
    # 獲取事件循環
    loop = asyncio.get_event_loop()
    # 創建一個任務(Future對象),沒綁定任何行爲,則這個任務永遠不知道什麼時候結束。
    fut = loop.create_future()

    # 創建一個任務(Task對象),綁定了set_after函數,函數內部在2s之後,會給fut賦值。
    # 即手動設置future任務的最終結果,那麼fut就可以結束了。
    
    loop.create_task(func(fut=fut))
    # 等待 Future對象獲取 最終結果,否則一直等下去
    return await fut



ret = asyncio.run(func1())
print(ret)

Future對象本身函數進行綁定,所以想要讓事件循環獲取Future的結果,則需要手動設置。而Task對象繼承了Future對象,其實就對Future進行擴展,他可以實現在對應綁定的函數執行完成之後,自動執行set_result,從而實現自動結束。

雖然,平時使用的是Task對象,但對於結果的處理本質是基於Future對象來實現的。

擴展:支持 await 對象語 法的對象課成爲可等待對象,所以 協程對象Task對象Future對象 都可以被成爲可等待對象

六、futures.Future對象

在Python的concurrent.futures模塊中也有一個Future對象,這個對象是基於線程池和進程池實現異步操作時使用的對象。

import time
from concurrent.futures import Future
from concurrent.futures.thread import ThreadPoolExecutor
from concurrent.futures.process import ProcessPoolExecutor


def func(value):
    time.sleep(1)
    print(value)


pool = ThreadPoolExecutor(max_workers=5)
# 或 pool = ProcessPoolExecutor(max_workers=5)


for i in range(10):
    fut = pool.submit(func, i)
    print(fut)

兩個Future對象是不同的,他們是爲不同的應用場景而設計,例如:concurrent.futures.Future不支持await語法 等。

在Python提供了一個將futures.Future 對象包裝成asyncio.Future對象的函數 asynic.wrap_future

接下里你肯定問:爲什麼python會提供這種功能?

其實,一般在程序開發中我們要麼統一使用 asycio 的協程實現異步操作、要麼都使用進程池和線程池實現異步操作。但如果 協程的異步進程池/線程池的異步 混搭時,那麼就會用到此功能了。

import time
import asyncio
import concurrent.futures

def func1():
    # 某個耗時操作
    time.sleep(2)
    return "SB"

async def main():
    loop = asyncio.get_running_loop()

    # 1. Run in the default loop's executor ( 默認ThreadPoolExecutor )
    # 第一步:內部會先調用 ThreadPoolExecutor 的 submit 方法去線程池中申請一個線程去執行func1函數,並返回一個concurrent.futures.Future對象
    # 第二步:調用asyncio.wrap_future將concurrent.futures.Future對象包裝爲asycio.Future對象。
    # 因爲concurrent.futures.Future對象不支持await語法,所以需要包裝爲 asycio.Future對象 才能使用。
    fut = loop.run_in_executor(None, func1)
    result = await fut
    print('default thread pool', result)

    # 2. Run in a custom thread pool:
    # with concurrent.futures.ThreadPoolExecutor() as pool:
    #     result = await loop.run_in_executor(
    #         pool, func1)
    #     print('custom thread pool', result)

    # 3. Run in a custom process pool:
    # with concurrent.futures.ProcessPoolExecutor() as pool:
    #     result = await loop.run_in_executor(
    #         pool, func1)
    #     print('custom process pool', result)

asyncio.run(main())

應用場景:當項目以協程式的異步編程開發時,如果要使用一個第三方模塊,而第三方模塊不支持協程方式異步編程時,就需要用到這個功能,例如:

import asyncio
import requests


async def download_image(url):
    # 發送網絡請求,下載圖片(遇到網絡下載圖片的IO請求,自動化切換到其他任務)
    print("開始下載:", url)

    loop = asyncio.get_event_loop()
    # requests模塊默認不支持異步操作,所以就使用線程池來配合實現了。
    future = loop.run_in_executor(None, requests.get, url)

    response = await future
    print('下載完成')
    # 圖片保存到本地文件
    file_name = url.rsplit('_')[-1]
    with open(file_name, mode='wb') as file_object:
        file_object.write(response.content)


if __name__ == '__main__':
    url_list = [
        'https://www3.autoimg.cn/newsdfs/g26/M02/35/A9/120x90_0_autohomecar__ChsEe12AXQ6AOOH_AAFocMs8nzU621.jpg',
        'https://www2.autoimg.cn/newsdfs/g30/M01/3C/E2/120x90_0_autohomecar__ChcCSV2BBICAUntfAADjJFd6800429.jpg',
        'https://www3.autoimg.cn/newsdfs/g26/M0B/3C/65/120x90_0_autohomecar__ChcCP12BFCmAIO83AAGq7vK0sGY193.jpg'
    ]

    tasks = [download_image(url) for url in url_list]

    loop = asyncio.get_event_loop()
    loop.run_until_complete( asyncio.wait(tasks) )

七、異步迭代器

什麼是異步迭代器

實現了 __aiter__()__anext__() 方法的對象。__anext__ 必須返回一個 awaitable 對象。async for 會處理異步迭代器的 __anext__() 方法所返回的可等待對象,直到其引發一個 StopAsyncIteration 異常。由 PEP 492 引入。

什麼是異步可迭代對象?

可在 async for 語句中被使用的對象。必須通過它的 __aiter__() 方法返回一個 asynchronous iterator。由 PEP 492 引入。

import asyncio


class Reader(object):
    """ 自定義異步迭代器(同時也是異步可迭代對象) """

    def __init__(self):
        self.count = 0

    async def readline(self):
        await asyncio.sleep(1)
        self.count += 1
        if self.count == 100:
            return None
        return self.count

    def __aiter__(self):
        return self

    async def __anext__(self):
        val = await self.readline()
        if val == None:
            raise StopAsyncIteration
        return val


async def func():
    # 創建異步可迭代對象
    async_iter = Reader()

    # async for 必須要放在async def函數內,否則語法錯誤。
    async for item in async_iter:
        print(item)

asyncio.run(func())

異步迭代器其實沒什麼太大的作用,只是支持了async for語法而已

八、異步上下文管理器

此種對象通過定義 __aenter__()__aexit__() 方法來對 async with 語句中的環境進行控制。由 PEP 492 引入。

import asyncio


class AsyncContextManager:
	def __init__(self):
        self.conn = conn
        
    async def do_something(self):
        # 異步操作數據庫
        return 666

    async def __aenter__(self):
        # 異步鏈接數據庫
        self.conn = await asyncio.sleep(1)
        return self

    async def __aexit__(self, exc_type, exc, tb):
        # 異步關閉數據庫鏈接
		await asyncio.sleep(1)


async def func():
    async with AsyncContextManager() as f:
        result = await f.do_something()
        print(result)


asyncio.run(func())

這個異步的上下文管理器還是比較有用的,平時在開發過程中 打開、處理、關閉 操作時,就可以用這種方式來處理。

總結

在程序中只要看到asyncawait關鍵字,其內部就是基於協程實現的異步編程,這種異步編程是通過一個線程在IO等待時間去執行其他任務,從而實現併發。

以上就是異步編程的常見操作,內容參考官方文檔。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章