asyncio介紹
熟悉c#的同學可能知道,在c#中可以很方便的使用?async 和?await 來實現異步編程,那么在python中應該怎么做呢,其實python也支持異步編程,一般使用?asyncio 這個庫,下面介紹下什么是?asyncio :
asyncio 是用來編寫 并發 代碼的庫,使用?async/await 語法。?asyncio 被用作多個提供高性能?Python 異步框架的基礎,包括網絡和網站服務,數據庫連接庫,分布式任務隊列等等。?asyncio 往往是構建 IO 密集型和高層級 結構化 網絡代碼的最佳選擇。
asyncio中的基本概念
可以看見,使用asyncio庫我們也可以在python代碼中使用?async 和?await 。在?asyncio 中,有四個基本概念,分別是:
Eventloop
Eventloop 可以說是?asyncio 應用的核心,中央總控,?Eventloop 實例提供了注冊、取消、執行任務和回調 的方法。 簡單來說,就是我們可以把一些異步函數注冊到這個事件循環上,事件循環回循環執行這些函數(每次只能執行一個),如果當前正在執行的函數在等待I/O返回,那么事件循環就會暫停它的執行去執行其他函數。當某個函數完成I/O后會恢復,等到下次循環到它的時候就會繼續執行。
Coroutine
協程本質就是一個函數,
import asyncio import time async def a(): print('Suspending a') await asyncio.sleep(3) print('Resuming a') async def b(): print('Suspending b') await asyncio.sleep(1) print('Resuming b') async def main(): start = time.perf_counter() await asyncio.gather(a(), b()) print(f'{main.__name__} Cost: {time.perf_counter() - start}') if __name__ == '__main__': asyncio.run(main())
執行上述代碼,可以看到類似這樣的輸出:
Suspending a
Suspending b
Resuming b
Resuming a
main Cost: 3.0023356619999997
關于協程的具體介紹,可以參考我以前的文章python中的協程 不過以前的那種寫法,需要使用裝飾器,已經過時了。
Future
Future 是表示一個“未來”對象,類似于?javascript 中的?promise ,當異步操作結束后會把最終結果設置到這個?Future 對象上,?Future 是對協程的封裝。
>>> import asyncio >>> def fun(): ... print("inner fun") ... return 111 ... >>> loop = asyncio.get_event_loop() >>> future = loop.run_in_executor(None, fun) #這里沒有使用await inner fun >>> future #可以看到,fun方法狀態是pending._call_check_cancel() at /usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/futures.py:348]> >>> future.done() # 還沒有完成 False >>> [m for m in dir(future) if not m.startswith('_')] ['add_done_callback', 'cancel', 'cancelled', 'done', 'exception', 'get_loop', 'remove_done_callback', 'result', 'set_exception', 'set_result'] >>> future.result() #這個時候如果直接調用result()方法會報錯 Traceback (most recent call last): File " ", line 1, in asyncio.base_futures.InvalidStateError: Result is not set. >>> async def runfun(): ... result=await future ... print(result) ... >>>loop.run_until_complete(runfun()) #也可以通過 loop.run_until_complete(future) 來執行,這里只是為了演示await 111 >>> future >>> future.done() True >>> future.result() 111 Task
Eventloop 除了支持協程,還支持注冊?Future 和?Task 2種類型的對象,而?Future 是協程的封裝,?Future 對象提供了很多任務方法(如完成后的回調,取消,設置任務結果等等),但是一般情況下開發者不需要操作?Future 這種底層對象,而是直接用?Future 的子類?Task 協同的調度協程來實現并發。那么什么是?Task 呢?下面介紹下:
一個與?Future 類似的對象,可運行?Python 協程。非線程安全。?Task 對象被用來在事件循環中運行協程。如果一個協程在等待一個?Future 對象,?Task 對象會掛起該協程的執行并等待該?Future 對象完成。當該?Future 對象完成被打包的協程將恢復執行。 事件循環使用協同日程調度: 一個事件循環每次運行一個?Task 對象。而一個?Task 對象會等待一個?Future 對象完成,該事件循環會運行其他?Task 、回調或執行IO操作。
下面看看用法:
>>> async def a(): ... print('Suspending a') ... await asyncio.sleep(3) ... print('Resuming a') ... >>> task = asyncio.ensure_future(a()) >>> loop.run_until_complete(task) Suspending a Resuming a
asyncio中一些常見用法的區別
Asyncio.gather和asyncio.wait
我們在上面的代碼中用到過?asyncio.gather ,其實還有另外一種用法是?asyncio.wait ,他們都可以讓多個協程并發執行,那么他們有什么區別呢?下面介紹下。
>>> import asyncio >>> async def a(): ... print('Suspending a') ... await asyncio.sleep(3) ... print('Resuming a') ... return 'A' ... ... ... async def b(): ... print('Suspending b') ... await asyncio.sleep(1) ... print('Resuming b') ... return 'B' ... >>> async def fun1(): ... return_value_a, return_value_b = await asyncio.gather(a(), b()) ... print(return_value_a,return_value_b) ... >>> asyncio.run(fun1()) Suspending a Suspending b Resuming b Resuming a A B >>> async def fun2(): ... done,pending=await asyncio.wait([a(),b()]) ... print(done) ... print(pending) ... task=list(done)[0] ... print(task) ... print(task.result()) ... >>> asyncio.run(fun2()) Suspending b Suspending a Resuming b Resuming a {:1> result='A'>, :8> result='B'>} set() :1> result='A'> A
根據上述代碼,我們可以看出兩者的區別:
asyncio.gather 能收集協程的結果,而且會按照輸入協程的順序保存對應協程的執行結果,而?asyncio.wait 的返回值有兩項,第一項是完成的任務列表,第二項表示等待完成的任務列表。
asyncio.wait 支持接受一個參數?return_when ,在默認情況下,?asyncio.wait 會等待全部任務完成?(return_when='ALL_COMPLETED') ,它還支持?FIRST_COMPLETED (第一個協程完成就返回)和?FIRST_EXCEPTION (出現第一個異常就返回):
>>> async def fun2(): ... done,pending=await asyncio.wait([a(),b()],return_when=asyncio.tasks.FIRST_COMPLETED) ... print(done) ... print(pending) ... task=list(done)[0] ... print(task) ... print(task.result()) ... >>> asyncio.run(fun2()) Suspending a Suspending b Resuming b {:8> result='B'>} { :3> wait_for= ()]>>} :8> result='B'> B
一般情況下,用?asyncio.gather 就足夠了。
asyncio.create_task和loop.create_task以及asyncio.ensure_future
這三種方法都可以創建?Task ,從Python3.7開始可以統一的使用更高階的?asyncio.create_task .其實?asyncio.create_task 就是用的?loop.create_task .?loop.create_task 接受的參數需要是一個協程,但是?asyncio.ensure_future 除了接受協程,還可以是?Future 對象或者?awaitable 對象:
- 如果參數是協程,其底層使用?loop.create_task ,返回?Task 對象
- 如果是?Future 對象會直接返回
-
如果是一個?awaitable 對象,會?await 這個對象的?__await__ 方法,再執行一次?ensure_future ,最后返回?Task 或者?Future 。
所以?ensure_future 方法主要就是確保這是一個?Future 對象,一般情況下直接用?asyncio.create_task 就可以了。
注冊回調和執行同步代碼
可以使用?add_done_callback 來添加成功回調:
def callback(future): print(f'Result: {future.result()}') def callback2(future, n): print(f'Result: {future.result()}, N: {n}') async def funa(): await asyncio.sleep(1) return "funa" async def main(): task = asyncio.create_task(funa()) task.add_done_callback(callback) await task #這樣可以為callback傳遞參數 task = asyncio.create_task(funa()) task.add_done_callback(functools.partial(callback2, n=1)) await task if __name__ == '__main__': asyncio.run(main())
執行同步代碼
如果有同步邏輯,想要用?asyncio 來實現并發,那么需要怎么做呢?下面看看:
def a1(): time.sleep(1) return "A" async def b1(): await asyncio.sleep(1) return "B" async def main(): loop = asyncio.get_running_loop() await asyncio.gather(loop.run_in_executor(None, a1), b1()) if __name__ == '__main__': start = time.perf_counter() asyncio.run(main()) print(f'main method Cost: {time.perf_counter() - start}') # 輸出: main method Cost: 1.0050589740000002
可以使用?
run_into_executor
來將同步函數邏輯轉化成一個協程,第一個參數是要傳遞?concurrent.futures.Executor 實例的,傳遞?None 會選擇默認的?executor 。
總結
以上所述是小編給大家介紹的Python中的asyncio代碼詳解,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對腳本之家網站的支持!
如果你覺得本文對你有幫助,歡迎轉載,煩請注明出處,謝謝!
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
