2.4、User’s guide (Coroutines)
Coroutines
協程在 tornado 的非同步程式碼中是被推薦使用的。協程使用 python 的 yield 關鍵字去暫停和恢復執行,來替代回撥鏈。(合作輕量級執行緒如 gevent 有時也被叫做協程,但是在 tornado 中,所有的協程使用顯示的上下文切換被稱為非同步函式。)
協程和同步程式碼一樣簡單,但是不用花費一個執行緒的代價。通過減少上下文切換髮生地方的數量,使併發更容易思考。
Example
from tornado import gen
@gen.coroutine
def fetch_coroutine(url):
http_client = AsyncHTTPClient()
response = yield http_client.fetch(url)
return response.body
Python 3.5:async
and wait
python 3.5 介紹了 async 和 await 關鍵字(使用這兩個關鍵字的也被稱為原生協程)。
從 tornado 4.3 版本,你也可以使用它們替代絕大多數的 yield-based 協程。簡單的使用 async def foo() 代替使用 @gen.coroutine 裝飾器,並且 await 代替 yield。
剩下的文件仍然使用 yield 風格以相容舊版的 Python,但是 async 和 await 會執行的更快。
async def fetch_coroutine(url):
http_client = AsyncHTTPClient()
response = await http_client.fetch(url)
return response.body
yield 關鍵字比 await 更通用;
例如在一個 yield-based 的協程,可以 yield 一個 Futures 列表,但是在原生的協程,你必須用 tornado.gen.multi 將他們包裝起來。這也消除了同 concurrent.futures 的整合。
你可以使用 tornado.gen.convert_yielded 將 yield 的用法轉換為 await 進行使用。
async def f():
executor = concurrent.futures.ThreadPoolExecutor()
await tornado.gen.convert_yielded(executor.submit(g))
How it works
一個函式包含了 yield,那麼這個函式就是一個生成器。所有的生成器都是非同步的,當呼叫時,他們返回一個生成器物件而不是執行完成。
@gen.coroutine 裝飾器和生成器通過 yield 表示式,給協程的呼叫者返回一個 Future。
- 簡單版本的協程內部迴圈
# Simplified inner loop of tornado.gen.Runner
def run(self):
# send(x) makes the current yield return x.
# It returns when the next yield is reached
future = self.gen.send(self.next)
def callback(f):
self.next = f.result()
self.run()
future.add_done_callback(callback)
裝飾器從生成器獲取了 Future 物件,等待(沒有阻塞) Future 執行完成,然後將結果作為 yield 表示式傳送給生成器。
大多數非同步程式碼從未直接接觸 Future 類,除了通過非同步函式的 yield 表示式獲取的 Future。
How to call a coroutine
協程不是通過正常的方式報出異常,他們提出的任何異常直到他被 yielded 將會被包裝在 Future 裡。
這意味著需要使用正確的方式去呼叫協程,否則可能忽略異常錯誤。
@gen.coroutine
def divide(x, y):
return x / y
def bad_call():
# This should raise a ZeroDivisionError, but it won't because
# the coroutine is called incorrectly.
# 這裡應該要報 ZeroDivisionError 錯誤
divide(1, 0)
幾乎所有的情況下,任何呼叫協程的函式本身也是協程,並且使用 yield 呼叫。
當你覆蓋一個父類中定義的方法時,查閱文件確認協程是否被允許(文件應該說這個方法可能是一個協程或可能返回一個 Future)
@gen.coroutine
def good_call():
# yield will unwrap the Future returned by divide() and raise
# the exception.
yield divide(1, 0)
有時候你想執行後放任不管一個協程(不等待它的執行結果),這種情況下,建議使用 IOLoop.spawn_callback,使 IOLoop 負責呼叫。
如果執行失敗了,會將日誌堆疊跟蹤。
IOLoop.current().spawn_callback(divide, 1, 0)
使用 @gen.coroutine 的函式推薦使用 IOLoop.spawn_callback。
使用 async 的函式只能使用 IOLoop.spawn_callback (否則協程不會執行)
最後,在程式的頂層,如果 IOLoop 還沒有執行,你可以通過 IOLoop.run_sync 開始 IOLoop,執行協程,然後停止 IOLoop 。通常用於啟動一個面向批處理程式的主函式。
# run_sync() doesn't take arguments, so we must wrap the
# call in a lambda.
IOLoop.current().run_sync(lambda: divide(1, 0))
Coroutine patterns
- Calling blocking functions
在協程裡面呼叫阻塞函式最簡單的方式是使用 IOLoop.run_in_executor,返回值是 Futures 相容協程。
@gen.coroutine
def call_blocking():
yield IOLoop.current().run_in_executor(blocking_func, args)
- Parallelism
協程裝飾器可以識別 value 值是 Futures 的列表和字典,並且等待所有的 Futures 並行
@gen.coroutine
def parallel_fetch(url1, url2):
resp1, resp2 = yield [http_client.fetch(url1),
http_client.fetch(url2)]
@gen.coroutine
def parallel_fetch_many(urls):
responses = yield [http_client.fetch(url) for url in urls]
# responses is a list of HTTPResponses in the same order
@gen.coroutine
def parallel_fetch_dict(urls):
responses = yield {url: http_client.fetch(url)
for url in urls}
# responses is a dict {url: HTTPResponse}
當使用 await 時,列表和字典必須使用 tornado.gen.multi 包裝
async def parallel_fetch(url1, url2):
resp1, resp2 = await gen.multi([http_client.fetch(url1),
http_client.fetch(url2)])
- Interleaving
有時候儲存一個 Future 而不是馬上使用 yield 是非常有效的,這樣你可以在等待前開始其他的操作。
@gen.coroutine
def get(self):
fetch_future = self.fetch_next_chunk()
while True:
chunk = yield fetch_future
if chunk is None: break
self.write(chunk)
fetch_future = self.fetch_next_chunk()
yield self.flush()
這種模式常在 @gen.coroutine 中使用。
如果 fetch_next_chunk() 是 async 函式,那麼上述寫法需變更為 fetch_future = tornado.gen.convert_yielded(self.fetch_next_chunk()) 來啟動後臺處理。
- Looping
在原生協程中,async for 可以使用。
在舊版的 python 中,迴圈是棘手的,因為沒有辦法使用 yield 去迭代並且捕捉 for 或者 while 的迴圈的結果,你需要將迴圈條件拆解來獲取結果,舉例如下:
import motor
db = motor.MotorClient().test
@gen.coroutine
def loop_example(collection):
cursor = db.collection.find()
while (yield cursor.fetch_next):
doc = cursor.next_object()
- Running in the background
PeriodCallback 通常不用在協程,相反的,協程可以使用 while Ture: 迴圈並且使用 tornado.gen.sleep
@gen.coroutine
def minute_loop():
while True:
yield do_something()
yield gen.sleep(60)
# Coroutines that loop forever are generally started with
# spawn_callback().
IOLoop.current().spawn_callback(minute_loop)
上一個例子中,迴圈每 60+N 秒執行一次,N 是 do_something() 消耗的時間。如果需要每 60 秒執行一次,使用如下模式:
@gen.coroutine
def minute_loop2():
while True:
nxt = gen.sleep(60) # Start the clock.
yield do_something() # Run while the clock is ticking.
yield nxt # Wait for the timer to run out.
上一篇: 2.3、User’s guide (Queue)
下一篇: 2.5、User’s guide (Structure of a Tornado web application)
相關文章
- OCFS2 user's guideGUIIDE
- oracle database backup and recovery user's guide part IVOracleDatabaseGUIIDE
- oracle database backup and recovery user's guide part IIIOracleDatabaseGUIIDE
- Backup And Recovery User's Guide-RMAN TSPITR模型GUIIDE模型
- oracle database backup and recovery user's guide part VII & VIIIOracleDatabaseGUIIDE
- oracle database backup and recovery user's guide part V & VIOracleDatabaseGUIIDE
- oracle database backup and recovery user's guide part I & IIOracleDatabaseGUIIDE
- Backup And Recovery User's Guide-RMAN資料修復概念GUIIDE
- Backup And Recovery User's Guide-禁用塊改變跟蹤GUIIDE
- b10807(PLSQL User s Guide and Reference).txtSQLGUIIDE
- Backup And Recovery User's Guide-建立和更新增量備份GUIIDE
- Backup And Recovery User's Guide-聯機重做日誌切換GUIIDE
- Backup And Recovery User's Guide-RMAN備份概念-增量備份GUIIDE
- Backup And Recovery User's Guide-RMAN架構-RMAN庫(Repository)GUIIDE架構
- Backup And Recovery User's Guide-RMAN TSPITR的基本概念GUIIDE
- Backup And Recovery User's Guide-執行閃回刪除操作GUIIDE
- Backup And Recovery User's Guide-閃回技術基本概念GUIIDE
- DUL User's and Configuration Guide V9.1.0.0 (Metalink)GUIIDE
- Backup And Recovery User's Guide-在還原之前驗證備份GUIIDE
- Backup And Recovery User's Guide-進行臨時歸檔備份GUIIDE
- Backup And Recovery User's Guide-RMAN架構-關於RMAN環境GUIIDE架構
- Backup And Recovery User's Guide-介質恢復問題解決GUIIDE
- Backup And Recovery User's Guide-執行完全資料庫恢復GUIIDE資料庫
- Backup And Recovery User's Guide-使用RECOVER命令的自動恢復GUIIDE
- Backup And Recovery User's Guide-TSPITR 約束、特定情形、限制GUIIDE
- Backup And Recovery User's Guide-開啟和禁用塊改變跟蹤GUIIDE
- Backup And Recovery User's Guide-備份歸檔重做日誌檔案GUIIDE
- Backup And Recovery User's Guide-使用RMAN備份歸檔重做日誌GUIIDE
- Backup And Recovery User's Guide-RMAN備份概念-備份保留期策略GUIIDE
- Backup And Recovery User's Guide-RMAN架構-RMAN通道-通道和裝置GUIIDE架構
- Backup And Recovery User's Guide-從RMAN開始-恢復表空間GUIIDE
- Backup And Recovery User's Guide-從RMAN開始-指令碼化RMAN操作GUIIDE指令碼
- Backup And Recovery User's Guide-從RMAN開始-備份資料庫GUIIDE資料庫
- Backup And Recovery User's Guide-從RMAN開始-概覽RMAN環境GUIIDE
- Backup And Recovery User's Guide-覆蓋歸檔日誌目的地GUIIDE
- Backup And Recovery User's Guide-使用SET AUTORECOVERY進行自動恢復GUIIDE
- Backup And Recovery User's Guide-對於SUSPENDED資料庫的備份GUIIDE資料庫
- Backup And Recovery User's Guide-執行完全自動的RMAN TSPITRGUIIDE