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)
相關文章
- EVASH Ultra EEPROM Development Board User GuidedevGUIIDE
- The hater’s guide to KubernetesGUIIDE
- Merchant‘s Guide to the GalaxyGUIIDE
- MT6761_MT62_MT65_ MMD User GuideGUIIDE
- Oracle GoldenGate 11g官方文件Administrator’s GuideOracleGoGUIIDE
- [譯] Room ? CoroutinesOOM
- Oracle GoldenGate 11g官方文件Administrator’s Guide續二OracleGoGUIIDE
- Oracle GoldenGate 11g官方文件Administrator’s Guide續一OracleGoGUIIDE
- Oracle GoldenGate 11g官方文件Administrator’s Guide續三OracleGoGUIIDE
- Oracle GoldenGate 11g官方文件Administrator’s Guide續四OracleGoGUIIDE
- Oracle 19c DBA's Guide(01): Getting Started with Database AdministrationOracleGUIIDEDatabase
- Kotlin Coroutines 1.5: GlobalScopeKotlin
- [譯]2.4-Key-Value Coding Programming Guide 官方文件第二部分第4節GUIIDE
- SAP S/4HANA key user tool extensibility原理
- How to make sense of Kotlin coroutinesKotlin
- Kotlin Coroutines 筆記 (二)Kotlin筆記
- Kotlin Coroutines 筆記 (一)Kotlin筆記
- 爬取《The Hitchhiker’s Guide to Python!》python進階書並製成pdfGUIIDEPython
- Flexbox GuideFlexGUIIDE
- A guide to this in JavaScriptGUIIDEJavaScript
- Meterpreter GuideGUIIDE
- Kotlin Coroutines(協程)講解Kotlin
- Guide to app architectureGUIIDEAPP
- crontab usage guideGUIIDE
- 實用教程丨使用K3s和MySQL執行Rancher 2.4MySql
- Guide to Database as a Service (DBaaS)GUIIDEDatabase
- webpack Performance: The Comprehensive GuideWebORMGUIIDE
- MariaDB ubuntu install guideUbuntuGUIIDE
- 【Java】A Guide to the Java ExecutorServiceJavaGUIIDE
- PEP 492 — Coroutines with async and await syntax 翻譯AI
- Spring Boot + Kotlin + Coroutines應用演示程式Spring BootKotlin
- 2.4 介面
- 如何使用Key User Tool擴充套件SAP S/4HANA Fiori UI套件UI
- Kotlin Coroutines Flow 系列(四) 執行緒操作Kotlin執行緒
- The Definitive Guide to CentOS.pdfGUIIDECentOS
- Notes about Vue Style GuideVueGUIIDE
- An easy guide to object rest/spreadGUIIDEObjectREST
- 2.4-2.10