異步程序和普通的連續程序(也就是同步程序)是很不同的,這裏會列出一些常見的陷阱,並介紹如何去避開他們。python
咱們用asyncio
就是爲了提升性能,而爲了更容易去開發編寫異步的代碼,咱們須要開啓debug
模式安全
在應用中開啓調試模式:網絡
PYTHONASYNCIODEBUG=1
,或者直接調用AbstractEventLoop.set_debug()
asynico logger
的日誌等級爲DEBUG,如在代碼開頭logging.basicConfig(level=logging.DEBUG)
warnings
模塊去顯示ResourceWarning
警告,如在命令行中添加-Wdefault
這個選項去啓動python來顯示這些取消任務(執行)這個操做對於普通的程序來說並不常見,可是在異步程序中,這不只是個很普通的事情,並且咱們還須要去準備好去處理它們。異步
能夠直接用Future.cancel()
這個方法去取消掉Future
類和tasks
類,而wait_for
這個方法則是直接在超時的時候取消掉這些任務。如下是一些間接取消任務的狀況async
若是在Future
被取消以後調用它的set_result()
或者set_exception()
,它將會被一個異常致使執行失敗(後半句原句爲: it would fail with an exception)函數
if not fut.cancelled(): fut.set_result('done')
不要直接經過AbstractEventLoop.call_soon()
去安排future
類調用set_result()
或者set_exception()
,由於這個future
類能夠在調用這些方法前被取消掉oop
若是你等待一個future
類的返回,那麼就應該提前檢查這個future
類是否被取消了,這樣能夠避免無用的操做性能
@coroutine def slow_operation(fut): if fut.cancelled(): return # ... slow computtaion ... yield from fut # ...
shield()
方法可以用來忽略取消操做ui
一個事件循環是跑在一個線程上面的,它全部的回溯函數和任務也是執行在這個線程上的。當事件循環裏面跑着一個任務的時候,不會有其餘任務同時跑在同一個線程裏,可是當這個任務用了yield from
以後,那麼這個任務將會被掛起,並而事件循環會去執行下一個任務。命令行
在不是同一線程的時候,應該用AbstractEventLoop.call_soon_threadsafe()
方法來安排回溯。
loop.call_soon_threadsafe(callback, *args)
大多數asyncio
對象都不是線程安全的。因此須要注意的是是否有在事件循環以外調用這些對象。舉個栗子,要取消一個future
類的時候,不該該直接調用Future.cancel()
,而是loop.call_soon_threadsafe(fut.cancel)
爲了控制信號和執行子進程,事件循環必須運行在主線程。
在不一樣線程安排回溯對象時,應該用run_coroutine_threadsafe()
,它會返回一個concurrent.futures.Future
對象去拿到結果
future = asyncio.run_coroutine_threadsafe(coro_func(), loop) result = future.result(timeout) # wait for the result with a timeout
AbstractEventLoop.run_in_executor()
可以用來做爲線程池的執行者,在不一樣的線程裏去執行回溯,並且不會阻塞當前線程的事件循環。
阻塞函數不該該被直接調用。舉個栗子,若是一個函數被阻塞了一秒,那麼其餘任務都會被延遲一秒,這會產生很大的影響。
在網絡和子進程方面,asynico
提供了高級的API如協議。
AbstractEventLoop.run_in_executor()
方法可以在不阻塞當前線程的事件循環的前提下,去調用其餘線程或者進程的任務。
asyncio
模塊的日誌信息在logging
模塊的asyncio
實例裏
默認的日誌等級是info,能夠根據本身的須要改變
logging.getLogger('asyncio').setLevel(logging.WARNING)
當一個協程函數被調用,而後若是它的返回值沒有傳給ensure_future()
或者AbstractEvenLoop.create_task()
的話,執行這個操做的協程對象將永遠不會被安排執行,這可能就是一個bug,能夠開啓調試模式去經過warning信息找到它。
舉個栗子
import asyncio @asyncio.coroutine def test(): print('never scheduled') test()
在調試模式下會輸出
Coroutine test() at test.py:3 was never yielded from Coroutine object create at (most recent call last): File 'test.py', line 7, in <module> test()
解決方法就是去調用ensure_future()
函數或者經過協程對象去調用AbstractEventLoop.create_task()
python常常會調用sys.displayhook()
來處理那些未經處理過的異常。若是Future.set_exception()
被調用,那麼這個異常將不會被消耗掉(處理掉),sys.displayhook()
也不會被調用。取而代之的是,當這個future
類被垃圾回收機制刪除的時候,日誌將會輸出這個異常的相關錯誤信息。
舉個栗子,unhandled exception
import asyncio @asyncio.coroutine def bug(): raise Exception('not consumed') loop = asyncio.get_event_loop() asyncio.ensure_future(bug()) loop.run_forever() loop.close()
輸出將是
Task exception was never retrieved future: <Task finished coro=<coro() done, defined at asyncio/coroutines.py:139> exception=Exception('not consumed',)> Traceback (most recent call last): File "asyncio/tasks.py", line 237, in _step result = next(coro) File "asyncio/coroutines.py", line 141, in coro res = func(*args, **kw) File "test.py", line 5, in bug raise Exception("not consumed") Exception: not consumed
開啓asyncio
的調試模式後,會獲得具體位置的錯誤信息
Task exception was never retrieved future: <Task finished coro=<bug() done, defined at test.py:3> exception=Exception('not consumed',) created at test.py:8> source_traceback: Object created at (most recent call last): File "test.py", line 8, in <module> asyncio.ensure_future(bug()) Traceback (most recent call last): File "asyncio/tasks.py", line 237, in _step result = next(coro) File "asyncio/coroutines.py", line 79, in __next__ return next(self.gen) File "asyncio/coroutines.py", line 141, in coro res = func(*args, **kw) File "test.py", line 5, in bug raise Exception("not consumed") Exception: not consumed
能夠看到第二行那裏,指出了拋出異常的位置
如下提供了幾個方法來解決這個問題。第一個方法就是將這個協程鏈到另一個協程並使用try/except去捕捉
@asyncio.coroutine def handle_exception(): try: yield from bug() except Exception: print("exception consumed") loop = asyncio.get_event_loop() asyncio.ensure_future(handle_exception()) loop.run_forever() loop.close()
第二個方法就是用AbstractEventLoop.run_until_complete()
task = asyncio.ensure_future(bug()) try: loop.run_until_complete(task) except Exception: print("exception consumed")
當一個協程函數被另一個協程函數(任務)調用,他們之間應該明確地用yield from
連接起來,否則的話,執行順序不必定和預想一致。
舉個栗子,用asyncio.sleep()
模仿的慢操做致使的bug
import asyncio @asyncio.coroutine def create(): yield from asyncio.sleep(3.0) print('(1) create file') @asyncio.coroutine def write(): yield from asyncio.sleep(1.0) print('(2) write into file') @asyncio.coroutine def close(): print('(3) close file') @asyncio.coroutine def test(): asyncio.ensure_future(create()) asyncio.ensure_future(write()) asyncio.ensure_future(close()) yield from asyncio.sleep(2.0) loop.stop() loop = asyncio.get_event_loop() asyncio.ensure_future(test()) loop.run_forever() print('Pending tasks at exit: %s' % asyncio.Task.all_tasks(loop)) loop.close()
預想的輸出爲
(1) create file (2) write into file (3) close file Pending tasks at exit: set()
實際的輸出爲
(3) close file (2) write into file Pending tasks at exit: {<Task pending create() at test.py:7 wait_for=<Future pending cb=[Task._wakeup()]>>} Task was destroyed but it is pending! task: <Task pending create() done at test.py:5 wait_for=<Future pending cb=[Task._wakeup()]>>
事件循環在create()
完成前就已經中止掉了,並且close()
在write()
以前被調用,然而咱們所但願調用這些函數的順序爲create()
,write()
,close()
爲了解決這個問題,必需要用yield from
來處理這些任務
@asyncio.corotine def test(): yield from asyncio.ensure_future(create()) yield from asyncio.ensure_future(write()) yield from asyncio.ensure_future(close()) yield from asyncio.sleep(2.0) loop.stop()
或者能夠不要asyncio.ensure_future()
@asyncio.coroutine def test(): yield from create() yield from write() yield from close() yield from asyncio.sleep(2.0) loop.stop()
若是掛起的任務被摧毀掉的話,那麼包裹它的協程的執行操做將不會完成。這頗有可能就是個bug,因此會被warning級別日誌記錄到
舉個栗子,相關的日誌
Task was destroyed but it is pending! task: <Task pending coro=<kill_me() done, defined at test.py:5> wait_for=<Future pending cb=[Task._wakeup()]>>
開啓asyncio
的調試模式就能夠拿到在任務被建立出來的具體位置的錯誤信息,開啓調試模式後的相關日誌
Task was destroyed but it is pending! source_traceback: Object created at (most recent call last): File "test.py", line 15, in <module> task = asyncio.ensure_future(coro, loop=loop) task: <Task pending coro=<kill_me() done, defined at test.py:5> wait_for=<Future pending cb=[Task._wakeup()] created at test.py:7> created at test.py:15>
當一個傳輸(交互)再也不須要的時候,調用它自身的close()
方法來釋放內存,事件循環也必須明確地關閉掉(也就是要調用事件循環自身的close()
)
若是transport
或者event loop
沒有被顯示地關閉掉,ResourceWarning
的警告信息會傳給負責摧毀它的那個執行構件。默認狀況下,ResourceWarning
的警告信息會被忽略掉。