深究Python中的asyncio庫-函式的回撥與排程
在大部分的高階語言中都有回撥函式,這裡我們看下asyncio中的的函式回撥。
成功回撥
可以給Task(Future)新增回撥函式,等Task完成後就會自動呼叫這個(些)回撥:
async def a(): await asyncio.sleep(1) return 'A' In : loop = asyncio.get_event_loop() In : task = loop.create_task(a()) In : def callback(future): ...: print(f'Result: {future.result()}') ...: In : task.add_done_callback(callback) In : await task Result: A Out: 'A'
可以看到在任務完成後執行了callback函式。我這裡順便解釋一個問題,不知道有沒有人注意到。
為什麼之前一直推薦大家用asyncio.create_task,但是很多例子卻用了loop.create_task?
這是因為在IPython裡面支援方便的使用await執行協程,但如果直接用asyncio.create_task會報「no running event loop」:
In : asyncio.create_task(a()) --------------------------------------------------------------------------- RuntimeError Traceback (most recent call last) <ipython-input-2-2a742a8da161> in <module> ----> 1 asyncio.create_task(a()) /usr/local/lib/python3.7/asyncio/tasks.py in create_task(coro) 322 Return a Task object. 323 """ --> 324 loop = events.get_running_loop() 325 return loop.create_task(coro) 326 RuntimeError: no running event loop
Eventloop是在單程式裡面的單執行緒中的,在IPython裡面await的時候會把協程註冊到一個執行緒的Eventloop上,但是REPL環境是另外一個執行緒,不是一個執行緒,所以會提示這個錯誤,即便asyncio.events._set_running_loop(loop)設定了loop,任務可以建立倒是不能await:因為task是線上程X的Eventloop上註冊的,但是await時卻到執行緒Y的Eventloop上去執行。這部分是C實現的,可以看延伸閱讀連結1。
所以現在你就會看到很多loop.create_task的程式碼片段,別擔心,在程式碼專案裡面都是用asyncio.create_task的,如果你非常想要在IPython裡面使用asyncio.create_task也不是沒有辦法,可以這樣做:
In : loop = asyncio.get_event_loop() In : def loop_runner(coro): ...: asyncio.events._set_running_loop(None) ...: loop.run_until_complete(coro) ...: asyncio.events._set_running_loop(loop) ...: In : %autoawait loop_runner In : asyncio.events._set_running_loop(loop) In : task = asyncio.create_task(a()) In : await task Out: 'A'
這樣就可以啦。我解釋下為什麼:
IPython裡面能執行await是由於loop_runner函式,這個函式能執行協程(延伸閱讀連結2),預設的效果大概是asyncio.get_event_loop().run_until_complete(coro)。為了讓asyncio.create_task正常執行我定義了新的loop_runner
透過autoawait這個magic函式就可以重新設定loop_runner
上面的報錯是「no running event loop」,所以透過events._set_running_loop(loop)設定一個正在執行的loop,但是在預設的loop_runner中也無法執行,會報「Cannot run the event loop while another loop is running」,所以重置await裡面那個running的loop,執行結束再設定回去。
如果你覺得有必要,可以在IPython配置檔案中設定這個loop_runner到c.InteractiveShell.loop_runner上~
好,我們說回來,add_done_callback方法也是支援引數的,但是需要用到functools.partial:
def callback2(future, n): print(f'Result: {future.result()}, N: {n}') In : task = loop.create_task(a()) In : task.add_done_callback(partial(callback2, n=1)) In : await task Result: A, N: 1 Out: 'A'
排程回撥
asyncio提供了3個按需回撥的方法,都在Eventloop物件上,而且也支援引數:
call_soon
在下一次事件迴圈中被回撥,回撥是按其註冊順序被呼叫的:
def mark_done(future, result): print(f'Set to: {result}') future.set_result(result) async def b1(): loop = asyncio.get_event_loop() fut = asyncio.Future() loop.call_soon(mark_done, fut, 'the result') loop.call_soon(partial(print, 'Hello', flush=True)) loop.call_soon(partial(print, 'Greeting', flush=True)) print(f'Done: {fut.done()}') await asyncio.sleep(0) print(f'Done: {fut.done()}, Result: {fut.result()}') In : await b1() Done: False Set to: the result Hello Greeting Done: True, Result: the result
這個例子輸出的比較複雜,我挨個分析:
call_soon可以用來設定任務的結果: 在mark_done裡面設定
透過2個print可以感受到call_soon支援引數。
最重要的就是輸出部分了,首先fut.done()的結果是False,因為還沒到下個事件迴圈,sleep(0)就可以切到下次迴圈,這樣就會呼叫三個call_soon回撥,最後再看fut.done()的結果就是True,而且fut.result()可以拿到之前在mark_done設定的值了
call_later
安排回撥在給定的時間(單位秒)後執行:
async def b2(): loop = asyncio.get_event_loop() fut = asyncio.Future() loop.call_later(2, mark_done, fut, 'the result') loop.call_later(1, partial(print, 'Hello')) loop.call_later(1, partial(print, 'Greeting')) print(f'Done: {fut.done()}') await asyncio.sleep(2) print(f'Done: {fut.done()}, Result: {fut.result()}') In : await b2() Done: False Hello Greeting Set to: the result Done: True, Result: the result
這次要注意3個回撥的延遲時間時間要<=sleep的,要不然還沒來及的回撥程式就結束了
call_at
安排回撥在給定的時間執行,注意這個時間要基於 loop.time() 獲取當前時間:
async def b3(): loop = asyncio.get_event_loop() now = loop.time() fut = asyncio.Future() loop.call_at(now + 2, mark_done, fut, 'the result') loop.call_at(now + 1, partial(print, 'Hello', flush=True)) loop.call_at(now + 1, partial(print, 'Greeting', flush=True)) print(f'Done: {fut.done()}') await asyncio.sleep(2) print(f'Done: {fut.done()}, Result: {fut.result()}') In : await b3() Done: False Hello Greeting Set to: the result Done: True, Result: the result
下一節:
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4822/viewspace-2837515/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- python中的回撥函式Python函式
- js中的回撥函式JS函式
- 對於Python中回撥函式的理解Python函式
- Python回撥函式Python函式
- js 中的submit 回撥函式JSMIT函式
- C++中的回撥函式C++函式
- 回撥函式 與 函式閉包函式
- 回撥函式的作用與意義函式
- Python/OpenCV:回撥函式PythonOpenCV函式
- 回撥函式的作用函式
- JavaScript:鉤子函式與回撥函式的區別JavaScript函式
- Swoole 回撥函式的註冊與呼叫函式
- 關於 js 中的回撥函式 callbackJS函式
- 理解javascript中的回撥函式(callback)【轉】JavaScript函式
- 聊一聊Vue中的回撥函式Vue函式
- 回撥函式函式
- 回撥函式的理解(一)函式
- Java回撥函式的理解Java函式
- delphi中回撥函式差異函式
- JavaScript 回撥函式JavaScript函式
- JavaScript回撥函式JavaScript函式
- JS—回撥函式JS函式
- 動畫回撥函式動畫函式
- java回撥函式Java函式
- 回撥函式(CallBack)函式
- [JS]回撥函式和回撥地獄JS函式
- 回撥函式,求積函式函式
- jquery回撥函式中this的指向簡單介紹jQuery函式
- Python技法3:匿名函式、回撥函式和高階函式Python函式
- C語言函式指標與回撥用函式C語言函式指標
- 函式指標&回撥函式Callback函式指標
- TLS回撥函式(Note)TLS函式
- java 回撥函式示例Java函式
- android回撥函式Android函式
- 函式回撥(C++)函式C++
- JavaScript回撥函式的高手指南JavaScript函式
- 更智慧的JavaScript回撥函式解析JavaScript函式
- C++回撥函式(callback)的使用C++函式