在python的異步實踐中,每次看asynicio
都會有新的收穫,本篇總結一下最近看這個庫的使用。
一些核心概念
異步函數的定義
普通函數的定義是使用 def
關鍵詞,異步的函數,協程函數(Coroutine)本質上是一個函數,特點是在代碼塊中可以將執行權交給其他協程,使用async def
來定義
1 2 3 4 5 6 7 8 9 |
# 普通函數定義 def add2(x): print(x+2) return x+2 # 異步函數的定義 async def add3(x): print("in async fun add") return x+3 |
如何調用協程並且得到它的運行結果?
調用普通的函數只需要 result = add2(2)
,這時函數就可以得到運行,並且將結果4返回給result,如果使用result = add3(2)
,此時再打印 result
呢?
得到的是一個coroutine對象,<coroutine object add3 at 0x000002ED564A5048>
,並不是2+3=5這個結果,怎樣才能得到結果呢?
協程函數想要執行需要放到事件循環裏執行。
事件循環 Eventloop
Eventloop 是asyncio應用的核心,把一些異步函數註冊到這個事件循環上,事件循環會循環執行這些函數,當執行到某個函數時,如果它正在等待I/O返回,如它正在進行網絡請求,或者sleep操作,事件循環會暫停它的執行去執行其他的函數;當某個函數完成I/O後會恢復,下次循環到它的時候繼續執行。因此,這些異步函數可以協同(Cooperative)運行:這就是事件循環的目標。
返回到上面的函數,想要得到函數執行結果,需要有一個Eventloop
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import asyncio loop = asyncio.get_event_loop() async def add3(x): print("in async fun add") return x+3 result = loop.run_until_complete(add3(2)) print(result) #運行的結果是 #in async fun add #5 |
或者使用await
關鍵字來修飾函數的調用,如result = await add3(2)
,但是await只能用在協程函數中,所以想要用await關鍵字就還需要定義一個協程函數
1 2 3 4 5 6 7 |
async def add3(x): print("in async fun add") return x+3 async def main(): result = await add3(2) return result |
但最終的執行還是需要放到一個事件循環中進行.
稍微複雜一點的例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
# coding:utf-8 import asyncio import time async def testa(x): print("in test a") await asyncio.sleep(3) print("Resuming a") return x async def testb(x): print("in test b") await asyncio.sleep(1) print('Resuming b') return x async def main(): start = time.time() resulta = await testa(1) resultb = await testb(2) print("test a result is %d"%resulta) print("test b result is %d"%resultb) print("use %s time"%(time.time()-start)) if __name__ == '__main__': loop = asyncio.get_event_loop() loop.run_until_complete(main()) |
這段代碼定義了兩個協程,並將它們放到另外一個協程main函數中,想要獲得它們運行的結果,事件循環的特點是當它遇到某個I/O需要等待(如這裏的asyncio.sleep()函數)的時候,可以去執行其它的函數,這樣,整個函數執行所需要的時間,應該是所有協程中執行時間最長的那個,對於上面這個代碼來說,一個sleep了3秒,一個sleep了1秒,總的用時應該是3秒多一點,但結果是這樣嗎?
它的輸出是這樣的
1 2 3 4 5 6 7 |
in test a Resuming a in test b Resuming b test a result is 1 test b result is 2 use 4.001966714859009 time |
它的用時是4秒多一點,而且是先執行了testa函數,然後再執行了testb函數,是串行的依次執行的,並沒有像我們想象中的併發執行。那應該怎樣才能併發執行呢?
1 2 3 4 5 6 7 8 9 10 11 |
async def main(): start = time.time() resulta,resultb = await asyncio.gather(testa(1),testb(2)) print("test a result is %d" % resulta) print("test b result is %d" % resultb) print("use %s time" % (time.time() - start)) if __name__ == '__main__': loop = asyncio.get_event_loop() loop.run_until_complete(main()) |
需要將協程放到asyncio.gather()
中運行,上面的代碼得到的輸出是
1 2 3 4 5 6 7 |
in test b in test a Resuming b Resuming a test a result is 1 test b result is 2 use 3.001237392425537 time |
可以看到,testa和testb是同步在運行,由於testb只sleep了1秒鐘,所以testb先輸出了Resuming b
,最後將每個協程函數的結果返回,注意,這裏是gather()函數裏的每一個協程函數都執行完了,它才結果,結果是一個列表,列表裏的值順序和放到gather函數裏的協程的順序是一致的。
除了使用asyncio.gather
來執行協程函數以外,還可以使用Task任務對象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
async def main(): start = time.time() taska = asyncio.ensure_future(testa(1)) taskb = asyncio.ensure_future(testb(2)) print(taska) print(taskb) print(taska.done(), taskb.done()) await taskb await taska print(taska.done(), taskb.done()) print(taskb.result()) print(taska.result()) print("use %s time" % (time.time() - start)) if __name__ == '__main__': loop = asyncio.get_event_loop() loop.run_until_complete(main()) |
使用asyncio.ensure_future(testa(1))
返回一個task對象,此時task進入pending狀態,並沒有執行,這時print(taska)
得到<Task pending coro=<testa() running at F:/python/python3Test/asynctest.py:7>>
些時,taska.done()返回False,表示它還沒有結束,當調用await taska
時表示開始執行該協程,當執行結束以後,taska.done() 返回True,這時可以調用taska.result() 得到函數的返回值,如果協程還沒有結束就調用result()方法則會拋個異常,raise InvalidStateError('Result is not ready.')
.
創建task對象除了使用asyncio.ensure_future()
方法還可以使用loop.create_task()
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
async def main(): start = time.time() taska = loop.create_task(testa(1)) taskb = loop.create_task(testb(2)) print(taska) print(taskb) print(taska.done(), taskb.done()) await taskb await taska print(taska.done(), taskb.done()) print(taskb.result()) print(taska.result()) print("use %s time" % (time.time() - start)) if __name__ == '__main__': loop = asyncio.get_event_loop() loop.run_until_complete(main()) |
上面一直在使用asyncio.gather()
函數來執行協程函數,還有一個asyncio.wait()
函數,它的參數是協程的列表。
1 2 3 4 5 6 7 8 9 10 11 12 |
async def main(): start = time.time() done,pending = await asyncio.wait([testa(1),testb(2)]) print(list(done)) print(list(pending)) print(list(done)[0].result()) print("use %s time" % (time.time() - start)) if __name__ == '__main__': loop = asyncio.get_event_loop() loop.run_until_complete(main()) |
asyncio.wait()
返回一個tuple對象,對象裏又包含一個已經完成的任務set和未完成任務的set,上面代碼得到的結果是
1 2 3 4 5 6 7 8 |
in test b in test a Resuming b Resuming a [<Task finished coro=<testa() done, defined at F:/python/python3Test/asynctest.py:7> result=1>, <Task finished coro=<testb() done, defined at F:/python/python3Test/asynctest.py:14> result=2>] [] 1 use 3.0003058910369873 time |
使用wait和gather有哪些區別呢?
首先,gather是需要所有任務都執行結束,如果某一個協程函數崩潰了,則會拋異常,都不會有結果。
wait可以定義函數返回的時機,可以是FIRST_COMPLETED(第一個結束的), FIRST_EXCEPTION(第一個出現異常的), ALL_COMPLETED(全部執行完,默認的)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
# coding:utf-8 import asyncio import time async def testa(x): print("in test a") await asyncio.sleep(3) print(1/0) print("Resuming a") return x async def testb(x): print("in test b") await asyncio.sleep(1) print(1/0) print('Resuming b') return x async def main(): start = time.time() done,pending = await asyncio.wait([testa(1),testb(2)],return_when=asyncio.tasks.FIRST_EXCEPTION) print(list(done)) print(list(pending)) print("use %s time" % (time.time() - start)) if __name__ == '__main__': loop = asyncio.get_event_loop() loop.run_until_complete(main()) |
這段代碼要求在出現第一個異常的時候就結果,函數整體不會崩潰,只是如果這裏想要獲取結果的話它是一個異常對象。
1 2 3 4 5 |
in test b in test a [<Task finished coro=<testb() done, defined at F:/python/python3Test/asynctest.py:16> exception=ZeroDivisionError('division by zero',)>] [<Task pending coro=<testa() running at F:/python/python3Test/asynctest.py:10> wait_for=<Future pending cb=[Task._wakeup()]>>] use 1.0000195503234863 time |
這篇文章介紹了一些asyncio的基本使用,之後會對Future和Task類做更深入的學習。
參考文章
深入理解asyncio(一)