異步編程
一、事件循環
我們可以理解爲就是一個 循環 並去檢測並執行一些代碼。
# 僞代碼
任務列表 = [ {'任務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
Future
is 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語法 等。
-
unlike asyncio Futures,
concurrent.futures.Future
instances cannot be awaited. -
asyncio.Future.result()
andasyncio.Future.exception()
do not accept the timeout argument. -
asyncio.Future.result()
andasyncio.Future.exception()
raise anInvalidStateError
exception when the Future is not done. -
Callbacks registered with
asyncio.Future.add_done_callback()
are not called immediately. They are scheduled withloop.call_soon()
instead. -
asyncio Future is not compatible with the
concurrent.futures.wait()
andconcurrent.futures.as_completed()
functions.
在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())
這個異步的上下文管理器還是比較有用的,平時在開發過程中 打開、處理、關閉 操作時,就可以用這種方式來處理。
總結
在程序中只要看到async
和await
關鍵字,其內部就是基於協程實現的異步編程,這種異步編程是通過一個線程在IO等待時間去執行其他任務,從而實現併發。
以上就是異步編程的常見操作,內容參考官方文檔。