asyncio 非同步編程

首先了解一下協程,協程的本質就是一條執行緒,多個任務在一條執行緒上來回切換,協程的所有切換都是基於用戶,只有在用戶級別才能感知到的 IO 才會用協程模組來規避,在 python 中主要使用的協程模組是 asyncio,並且基於 async 和 await 關鍵字的協程可以實現非同步編程,這也是目前 python 非同步相關的主流技術。

1.事件循環

事件循環它其實是非同步編程中的一個非常重要的環節,可以把它當成一個死循環,它會去檢查並執行一些程式碼。

示例:偽程式碼

任務列表 = [ 任務1, 任務2, 任務3,... ]

while True:
    可執行的任務列表,已完成的任務列表 = 去任務列表中檢查所有的任務,將'可執行'和'已完成'的任務返回
    
    for 就緒任務 in 已準備就緒的任務列表:
        執行已就緒的任務
    
    for 已完成的任務 in 已完成的任務列表:
        在任務列表中移除 已完成的任務
    
    如果 任務列表 中的任務都已完成,則終止循環

通過上述偽程式碼就會發現這個事件循環就是可以理解成一個死循環在檢查一個列表裡的任務,如果列表裡面的任務是可執行的,那就去執行這個任務,如果是不可執行(指的是遇到 IO 操作)的,那麼在檢查的時候就根本檢查不到,相當於把 這個任務忽略掉,認為它不需要被執行,讓它一直在等待著 IO 請求,當 IO 完成之後在去執行這個任務。

獲取和創建事件循環

import asyncio

# 生成和獲取一個事件循環
loop = asyncio.get_event_loop()

# 給事件循環添加任務,讓事件循環去檢測這個任務的狀態是否可運行
loop.run_until_complete(任務)

2.async

async 是一個關鍵字,用於定義一個協程函數。

協程函數:定義函數的時候使用 async def 函數名

協程對象:執行 協程函數() 得到的協程對象。

# 定義一個協程函數
async def func():
    pass

# 調用協程函數,返回一個協程對象
result = func()

調用協程函數的時候,函數內部的程式碼不會執行,只會返回一個協程對象。

如果想要運行協程函數內部程式碼,必須要將協程對象交給事件循環來處理。

import asyncio

async def func():
    print('這是一個協程函數!')
    
    
result = func

# 方式一:
loop = asyncio.get_event_loop()   # 生成一個事件循環
loop.run_until_complete( result ) # 將協程對象添加到事件循環執行


# 方式二:python 3.7 之後使用,本質上還是和上面一樣,但是比較簡單
asyncio.run( result )

3.await

await 也是一個關鍵字,它主要是在當前任務1遇到 IO 操作的時候切到其他沒有 IO 操作的任務2去執行,讓事件循環可以去執行其他任務,當任務1的 IO 操作執行完後再切換回來執行 await 之後的內容。

await 的後面只能加可等待的對象(協程對象、Task對象 ….)

示例:

import asyncio


async def others():
    print('others -----> start')
    await asyncio.sleep(2)
    print('others -----> end')
    return '返回值'


async def fun():
    print('執行協程函數內部程式碼!')
    
    # 遇到IO操作掛起當前協程(任務),等IO操作完成之後再繼續往下執行。當前協程掛起時,事件循環可以去執行其他協程(任務)。
    response = await others()	# 它會等有返回值了才會繼續往下執行
    print('IO請求結束,結果為:', response)

asyncio.run(fun())


# 輸出:
執行協程函數內部程式碼!
others -----> start
others -----> end
IO請求結束,結果為: 返回值

從上面這個示例可以看出來 await 就是等待對應後面的值得到結果之後,在向下繼續執行!

由於在這個示例中事件循環列表中只有一個任務,所以在 IO 等待時無法演示切換到其他任務的執行效果,在程式中如果想要創建多個任務對象,需要使用 Task 對象來實現。

4.Task對象

Task 用於並發調度協程,在事件循環中添加多個任務。

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

示例1:通過asyncio.create_task(協程對象)添加任務。

import asyncio


async def func(i):
    print(i, '--->start')
    await asyncio.sleep(1)
    print(i, '--->end')
    return f'返回值{i}'


async def main():
    print('main start')

    # 創建 Task 對象並添加到事件循環中
    task1 = asyncio.create_task(func(1))
    task2 = asyncio.create_task(func(2))

    print('main end')
	
    # 此處await會自動切換執行其他任務。例如:task1,task2
    ret1 = await task1
    ret2 = await task2
    print(ret1, ret2)

asyncio.run(main())

# 輸出:
main start
main end
1 --->start
2 --->start
1 --->end
2 --->end
返回值1 返回值2

示例2:通過asyncio.wait(協程對象列表)添加任務,在它的源碼中會通過 ensure_future 把每個協程封裝成 Task 對象。

import asyncio


async def func(i):
    print(i, '--->start')
    await asyncio.sleep(1)  # 當遇到IO操作掛起當前協程並切換其他協程
    print(i, '--->end')
    return f'返回值{i}'


task_list = [func(1), func(2)]

# 如果設置了 timeout 值,則意味著此處最多等待的秒,完成的協程返回值寫入done中,未完成的寫入pending
done, pending = asyncio.run(asyncio.wait(task_list, timeout=None))


# 輸出:
1 --->start
2 --->start
1 --->end
2 --->end
Tags: