摘要:譯者說于年月日發(fā)布,該版本正式支持的關鍵字,并且用舊版本編譯同樣可以使用這兩個關鍵字,這無疑是一種進步。其次,這是最后一個支持和的版本了,在后續(xù)的版本了會移除對它們的兼容。
譯者說
Tornado 4.3于2015年11月6日發(fā)布,該版本正式支持Python3.5的async/await關鍵字,并且用舊版本CPython編譯Tornado同樣可以使用這兩個關鍵字,這無疑是一種進步。其次,這是最后一個支持Python2.6和Python3.2的版本了,在后續(xù)的版本了會移除對它們的兼容。現(xiàn)在網絡上還沒有Tornado4.3的中文文檔,所以為了讓更多的朋友能接觸并學習到它,我開始了這個翻譯項目,希望感興趣的小伙伴可以一起參與翻譯,項目地址是tornado-zh on Github,翻譯好的文檔在Read the Docs上直接可以看到。歡迎Issues or PR。
協(xié)程Tornado中推薦使用協(xié)程寫異步代碼. 協(xié)程使用了Python的yield關鍵字代替鏈式回調來將程序掛起和恢復執(zhí)行(像在 gevent中出現(xiàn)的輕量級線程合作方式有時也被稱為協(xié)程,但是在Tornado中所有的協(xié)程使用明確的上下文切換,并被稱為異步函數(shù)).
使用協(xié)程幾乎像寫同步代碼一樣簡單, 并且不需要浪費額外的線程. 它們還通過減少上下文切換來 使并發(fā)編程更簡單.
例子:
from tornado import gen @gen.coroutine def fetch_coroutine(url): http_client = AsyncHTTPClient() response = yield http_client.fetch(url) # 在Python 3.3之前, 在generator中是不允許有返回值的 # 必須通過拋出異常來代替. # 就像 raise gen.Return(response.body). return response.bodyPython 3.5:async和await
Python 3.5引入了async和await關鍵字(使用這些關鍵字的函數(shù)也被稱為"原生協(xié)程").從Tornado 4.3,你可以用它們代替yield為基礎的協(xié)程.只需要簡單的使用async def foo()在函數(shù)定義的時候代替@gen.coroutine裝飾器,用await代替yield. 本文檔的其他部分會繼續(xù)使用yield的風格來和舊版本的Python兼容, 但是如果async和await可用的話,它們運行起來會更快:
async def fetch_coroutine(url): http_client = AsyncHTTPClient() response = await http_client.fetch(url) return response.body
await關鍵字比yield 關鍵字功能要少一些.例如,在一個使用 yield 的協(xié)程中,你可以得到Futures 列表, 但是在原生協(xié)程中,你必須把列表用 tornado.gen.multi 包起來. 你也可以使用 tornado.gen.convert_yielded來把任何使用yield工作的代碼轉換成使用await的形式.
雖然原生協(xié)程沒有明顯依賴于特定框架(例如它們沒有使用裝飾器,例如tornado.gen.coroutine或asyncio.coroutine), 不是所有的協(xié)程都和其他的兼容. 有一個coroutine runner在第一個協(xié)程被調用的時候進行選擇, 然后被所有用await直接調用的協(xié)程共享. Tornado的協(xié)程執(zhí)行者(coroutine runner)在設計上是多用途的,可以接受任何來自其他框架的awaitable對象;其他的協(xié)程運行時可能有很多限制(例如,asyncio協(xié)程執(zhí)行者不接受來自其他框架的協(xié)程).基于這些原因,我們推薦組合了多個框架的應用都使用Tornado的協(xié)程執(zhí)行者來進行協(xié)程調度.為了能使用Tornado來調度執(zhí)行asyncio的協(xié)程, 可以使用tornado.platform.asyncio.to_asyncio_future適配器.
它是如何工作的包含了yield關鍵字的函數(shù)是一個生成器(generator).所有的生成器都是異步的;當調用它們的時候,會返回一個生成器對象,而不是一個執(zhí)行完的結果.@gen.coroutine裝飾器通過yield表達式和生成器進行交流, 而且通過返回一個.Future與協(xié)程的調用方進行交互.
下面是一個協(xié)程裝飾器內部循環(huán)的簡單版本:
# tornado.gen.Runner 簡化的內部循環(huán) 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對象執(zhí)行完成, 然后"解開(unwraps)"這個Future對象,并把結果作為yield 表達式的結果傳回給生成器.大多數(shù)異步代碼從來不會直接接觸Future類.除非 Future立即通過異步函數(shù)返回給yield表達式.
如何調用協(xié)程協(xié)程一般不會拋出異常: 它們拋出的任何異常將被.Future捕獲直到它被得到.這意味著用正確的方式調用協(xié)程是重要的, 否則你可能有被忽略的錯誤:
@gen.coroutine def divide(x, y): return x / y def bad_call(): # 這里應該拋出一個 ZeroDivisionError 的異常, 但事實上并沒有 # 因為協(xié)程的調用方式是錯誤的. divide(1, 0)
幾乎所有的情況下, 任何一個調用協(xié)程的函數(shù)都必須是協(xié)程它自身, 并且在調用的時候使用yield關鍵字. 當你復寫超類中的方法, 請參閱文檔,看看協(xié)程是否支持(文檔應該會寫該方法"可能是一個協(xié)程"或者"可能返回一個 Future類 "):
@gen.coroutine def good_call(): # yield 將會解開 divide() 返回的 Future 并且拋出異常 yield divide(1, 0)
有時你可能想要對一個協(xié)程"一勞永逸"而且不等待它的結果. 在這種情況下,建議使用.IOLoop.spawn_callback, 它使得.IOLoop 負責調用. 如果它失敗了, .IOLoop會在日志中把調用棧記錄下來:
# IOLoop 將會捕獲異常,并且在日志中打印棧記錄. # 注意這不像是一個正常的調用, 因為我們是通過 # IOLoop 調用的這個函數(shù). IOLoop.current().spawn_callback(divide, 1, 0)
最后, 在程序頂層, 如果.IOLoop尚未運行, 你可以啟動.IOLoop,執(zhí)行協(xié)程,然后使用.IOLoop.run_sync方法停止.IOLoop. 這通常被用來啟動面向批處理程序的main函數(shù):
# run_sync() 不接收參數(shù),所以我們必須把調用包在lambda函數(shù)中. IOLoop.current().run_sync(lambda: divide(1, 0))協(xié)程模式 結合 callback
為了使用回調代替.Future與異步代碼進行交互, 把調用包在.Task類中. 這將為你添加一個回調參數(shù)并且返回一個可以yield的.Future :
@gen.coroutine def call_task(): # 注意這里沒有傳進來some_function. # 這里會被Task翻譯成 # some_function(other_args, callback=callback) yield gen.Task(some_function, other_args)調用阻塞函數(shù)
從協(xié)程調用阻塞函數(shù)最簡單的方式是使用concurrent.futures.ThreadPoolExecutor, 它將返回和協(xié)程兼容的Futures:
thread_pool = ThreadPoolExecutor(4) @gen.coroutine def call_blocking(): yield thread_pool.submit(blocking_func, args)并行
協(xié)程裝飾器能識別列表或者字典對象中各自的 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] # 響應是和HTTPResponses相同順序的列表 @gen.coroutine def parallel_fetch_dict(urls): responses = yield {url: http_client.fetch(url) for url in urls} # 響應是一個字典 {url: HTTPResponse}交叉存取
有時候保存一個 .Future 比立即yield它更有用, 所以你可以在等待之前
執(zhí)行其他操作:
@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()循環(huán)
協(xié)程的循環(huán)是棘手的, 因為在Python中沒有辦法在for循環(huán)或者while循環(huán)yield迭代器,并且捕獲yield的結果. 相反,你需要將循環(huán)條件從訪問結果中分離出來, 下面是一個使用Motor的例子:
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()在后臺運行
PeriodicCallback通常不使用協(xié)程. 相反,一個協(xié)程可以包含一個while True:循環(huán)并使用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)
有時可能會遇到一個更復雜的循環(huán). 例如, 上一個循環(huán)運行每次花費60+N秒,其中N是do_something()花費的時間. 為了準確的每60秒運行,使用上面的交叉模式:
@gen.coroutine def minute_loop2(): while True: nxt = gen.sleep(60) # 開始計時. yield do_something() # 計時后運行. yield nxt # 等待計時結束.
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/37707.html
摘要:譯者說于年月日發(fā)布,該版本正式支持的關鍵字,并且用舊版本編譯同樣可以使用這兩個關鍵字,這無疑是一種進步。其次,這是最后一個支持和的版本了,在后續(xù)的版本了會移除對它們的兼容。 譯者說 Tornado 4.3于2015年11月6日發(fā)布,該版本正式支持Python3.5的async/await關鍵字,并且用舊版本CPython編譯Tornado同樣可以使用這兩個關鍵字,這無疑是一種進步。其次...
閱讀 3213·2023-04-25 18:43
閱讀 892·2021-11-24 09:39
閱讀 1361·2021-10-14 09:43
閱讀 3890·2021-09-22 15:58
閱讀 1899·2019-08-29 17:18
閱讀 409·2019-08-29 14:14
閱讀 3078·2019-08-29 13:01
閱讀 1614·2019-08-29 12:33