深入理解yield(三):yield與基於Tornado的異步回調


轉自:http://beginman.cn/python/2015/04/06/yield-via-Tornado/

  • 作者:BeginMan
  • 版權聲明:本文版權歸作者所有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接。
  • 發表於 2015-04-06

 深入理解yield(二):yield與協程 和深入理解yield(一):yield原理已經對yield原理及在python中的運用了解了很多,那么接下來就要結合Tornado,進行python異步的分析。

一.異步的實現

異步的實現離不開回調函數,接下來介紹回調函數的概念以及在python中的使用。

軟件模塊之間總是存在着一定的接口,從調用方式上,可以把他們分為三類:同步調用、回調和異步調用。同步調用是一種阻塞式調用,調用方要等待對方執行完畢才返回,它是一種單向調用;回調是一種雙向調用模式,也就是說,被調用方在接口被調用時也會調用對方的接口;異步調用是一種類似消息或事件的機制,不過它的調用方向剛好相反,接口的服務在收到某種訊息或發生某種事件時,會主動通知客戶方(即調用客戶方的接口)。回調和異步調用的關系非常緊密,通常我們使用回調來實現異步消息的注冊,通過異步調用來實現消息的通知。同步調用是三者當中最簡單的,而回調又常常是異步調用的基礎,

可以參閱 同步調用、回調和異步調用區別加深理解。

圖片來源:https://www.ibm.com/developerworks/cn/linux/l-callback/

有時候對回調這個概念比較模糊,在知乎上回調函數(callback)是什么?,舉了一個很好的例子:

你到一個商店買東西,剛好你要的東西沒有貨,於是你在店員那里留下了你的電話,過了幾天店里有貨了,店員就打了你的電話,然后你接到電話后就到店里去取了貨。在這個例子里,你的電話號碼就叫回調函數,你把電話留給店員就叫登記回調函數,店里后來有貨了叫做觸發了回調關聯的事件,店員給你打電話叫做調用回調函數,你到店里去取貨叫做響應回調事件。

下面舉一個Python的回調例子

# coding=utf-8 __author__ = 'fang' def call_back(value): print 'call back value:', value def caller(func, arg): print 'caller' func(arg) caller(call_back, 'hello,world')

Tornado異步

tornado提供了一套異步機制,asynchronous裝飾器能夠使其異步,tornado默認在get()或者post()返回后自動結束HTTP請求(默認在函數處理返回時關閉客戶端的連接),當裝飾器給定,在函數返回時response不會結束,self.finish()去結束HTTP請求,它的主要工作就是將 RequestHandler 的 _auto_finish 屬性置為 false。

如下例子:

#同步阻塞版本 def MainHandler(tornado.web.RequestHandler): def get(self): client = tornado.httpclient.HttpClient() # 阻塞 response = client.fetch("http://www.google.com/") self.write('Hello World')

這個例子就不在啰嗦了,整體性能就在於訪問google的時間.下面展示異步非阻塞的例子:

def MainHandler(tornado.web.RequestHandler): @tornado.web.asynchronous def get(self): client = tornado.httpclient.AsyncHTTPClient() def callback(response): self.write("Hello World") self.finish() client.fetch("http://www.google.com/", callback)

fetch的時候提供callback函數,這樣當fetch http請求完成的時候才會去調用callback,而不會阻塞。callback調用完成之后通過finish結束與client的連接。

這種異步回調的缺點就是:拆分代碼邏輯,多重回調的繁瑣,能不能有一套方案像正常執行邏輯一樣使異步能夠順序化去執行呢?在上面的兩節yield的學習中可知:因為yield很方便的提供了一套函數掛起,運行的機制,所以我們能夠通過yield來將原本是異步的流程變成同步的。,在tornado中具體表現為tornado.gen.

def MainHandler(tornado.web.RequestHandler): @tornado.web.asynchronous @tornado.gen.engine def get(self): client = tornado.httpclient.AsyncHTTPClient() response = yield tornado.gen.Task(client.fetch, "http://www.google.com/") self.write("Hello World") self.finish()

使用gen.engine的decorator,該函數主要就是用來管理generator的流程控制。 使用了gen.Task,在gen.Task內部,會生成一個callback函數,傳給async fetch,並執行fetch,因為fetch是一個異步操作,所以會很快返回。 在gen.Task返回之后使用yield,掛起 當fetch的callback執行之后,喚醒掛起的流程繼續執行.

那么接下來分析gen源碼:

def engine(func): """Decorator for asynchronous generators. 異步generators裝飾器 任何從這個module生產的生成器必須被這個裝飾器所裝飾。這個裝飾器只用於已經是異步的函數 如: @tornado.web.asynchronous @tornado.gen.engine def get(RequestHandler): #http method. pass 源碼分析:http://blog.xiaogaozi.org/2012/09/21/understanding-tornado-dot-gen/ Any generator that yields objects from this module must be wrapped in this decorator. The decorator only works on functions that are already asynchronous. For `~tornado.web.RequestHandler```get``/``post``/etc methods, this means that both the `tornado.web.asynchronous` and `tornado.gen.engine` decorators must be used (for proper exception handling, ``asynchronous` should come before ``gen.engine``). In most other cases, it means that it doesn't make sense to use ``gen.engine`` on functions that don't already take a callback argument. """ @functools.wraps(func) def wrapper(*args, **kwargs): runner = None def handle_exception(typ, value, tb): # if the function throws an exception before its first "yield" # (or is not a generator at all), the Runner won't exist yet. # However, in that case we haven't reached anything asynchronous # yet, so we can just let the exception propagate. if runner is not None: return runner.handle_exception(typ, value, tb) return False with ExceptionStackContext(handle_exception) as deactivate: # 代表被裝飾的http method(如get), 因為在之前所裝飾的method 包含yield關鍵字,所以gen = func()是generator gen = func(*args, **kwargs) # 檢查是否是generator對象 if isinstance(gen, types.GeneratorType): # 雖然調用了包含yield的http method,但函數並沒有立即執行,只是賦值給了gen # 可想而知Runner()是來啟動生成器函數的,包含next(),send(),throw(),close()等方法 runner = Runner(gen, deactivate) runner.run() return assert gen is None, gen deactivate() # no yield, so we're done return wrapper

了解了gen,接下來我們自己實現一個:

# coding=utf-8 __author__ = 'fang' import tornado.ioloop from tornado.httpclient import AsyncHTTPClient import functools def task(fun, url): return functools.partial(fun, url) def callback(gen, response): try: print 'callback:', response gen.send(response) except StopIteration: pass def sync(func): def wrapper(): gen = func() f = gen.next() print 'aa', f, gen f(functools.partial(callback, gen)) return wrapper @sync def fetch(): response = yield task(AsyncHTTPClient().fetch, 'http://www.suhu.com') print '1' print response print '2' fetch() print 3 tornado.ioloop.IOLoop.instance().start()

輸出:

aa <functools.partial object at 0x10a992fc8> <generator object fetch at 0x10a6e6460> 3 callback: HTTPResponse(code=200,request_time=0.9294881820678711,buffer=<_io.BytesIO object at 0x10a9b9110>......) 1 HTTPResponse(code=200,request_time=0.9294881820678711,buffer=<_io.BytesIO object at 0x10a9b9110>......) 2

參考

1.學習tornado:異步

2.使用生成器展平異步回調結構

3.異步消息的傳遞-回調機制

4.理解 tornado.gen


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM