在tornado3發布之後,強化了coroutine的概念,在非同步程式設計中,取代了原來的gen.engine, 變成現在的gen.coroutine。這個裝飾器本來就是為了簡化在tornado中的非同步程式設計。避免寫回呼函數, 使得開發更符合正常邏輯思考。一個簡單的例子如下:
class MaindHandler(web.RequestHandler):
@asynchronous
yncHTTPClient()
resp = yield client .fetch(https://api.github.com/users")
if resp.code == 200:
resp = escape.json_decode p. (json.dumps(resp, indent=4, separators=(',', ':')))
else:
resp = {"message": "errornrite fp. json.dumps(resp, indent =4, separators={',', ':')))
self.finish()
在yield語句之後,ioloop將會註冊該事件,等到resp 這個過程是異步的。這裡使用json.dumps,而沒有使用tornado自帶的escape.json_encode,是因為在建立REST風格的API的時候,往往會從瀏覽器存取取得JSON格式的資料。 ,在瀏覽器端顯示查看的時候會更友善。的回答escape並不打算提供全部的json功能,使用者可以自己直接使用json模組。必須使用異步的函式庫。庫可以在這裡找到。包括用的比較多的MongoDB的Driver。
在3.0版本之後,gen.coroutine模組顯得比較突出。 coroutine裝飾器可以讓原本靠回呼的非同步程式看起來像是同步程式設計。其中便是利用了Python中生成器的Send函數。在生成器中,yield關鍵字往往會與正常函數中的return相比。它可以被當成迭代器,從而使用next()傳回yield的結果。但是生成器還有另一個用法,就是使用send方法。在生成器內部可以將yield的結果賦值給一個變量,而這個值是透過外部的生成器client來send的。舉個例子:def test_yield():
"__main__":
client.send("hello world")
輸出結果如下:
test yeildello它的client使用send方法,原來函數繼續運作。而這裡的gen.coroutine方法就是非同步執行所需的操作,然後等待結果回傳之後,再send到原函數,原函數則會繼續執行,這樣就以同步方式寫的程式碼達到了非同步執行的效果。
Tornado非同步程式設計@gen.coroutine
def post(self):
client = AsyncHTTPClient()
client = AsyncHTTPClient()
self.write(escape.json_encode(body))
self.finish()
;
@gen.coroutime
def post(self):
self.write(resp):
client = AsyncHTTPClient() resp = yield client.fetch("https://api.github.com/users") if resp.code == 200:p else: resp = {"message": "fetch client error"} logger.error("client fetch error %d, %s" resp. (resp)
這裡,當把非同步封裝在一個函數中的時候,並不是像普通程式那樣使用return關鍵字進行返回,gen模組提供了一個gen.Return的方法。是透過raise方法實現的。這個也是和它是使用生成器方式實現有關的。
使用coroutine跑定時任務
Tornado中有這麼一個方法:
tornado.ioloop.IOLitime)阻塞版本,它接受一個時間長度和一個函數這兩個參數。表示多少時間之後呼叫該函數。在這裡它是基於ioloop的,因此是非阻塞的。此方法在客戶端長連接以及回調函數程式設計中使用的比較多。但是用它來跑一些定時任務卻是無奈之舉。通常跑定時任務也沒必要使用到它。但是我在使用heroku的時候,發現沒有註冊信用卡的話僅僅能夠使用一個簡單Web Application的託管。不能加入定時任務來跑。於是就想出這麼一個方法。在這裡,我主要使用它隔一段時間透過Github API介面去抓取資料。大自使用方法如下:
裝飾器
def sync_loop_call(delta=60 * 1000):
.
""" def wrap_loop(func): @wraps(func) @gen.coroutine. options.logger.info("function %r start at %d" %(func.__name__, int(time.time()))) yield func(*args, **kwargs) except Exception, e: r error: %s" % "function %r end at %d" % (func.__name__, int(time.time)) )) wrap_func) return wrap_func return wrap_loop
return wrap_loop
(delta=10 * 1000)
def worker():
"""
Do something
__ name__ == "__main__":
worker()
app.listen(options.port)
tornado.ioloop.IOLoop.instance().start()
這樣做之後,當Web Application啟動之後,定時任務就會隨著跑起來,而且因為它是基於事件的,並且非同步執行的,所以並不會影響Web服務的正常運行,當然任務不能是阻塞的或計算密集型的。我這裡主要是抓取數據,而且用的是Tornado自帶的非同步抓取方法。
在sync_loop_call裝飾器中,我在wrap_func函數上加了@gen.coroutine裝飾器,這樣就保證只有yeild的函數執行完之後,才會執行add_timeout操作。如果沒有@gen.coroutine裝飾器。那麼不等到yeild返回,就會執行add_timeout了。
總結
Tornado是一個非阻塞的web伺服器以及web框架,但是在使用的時候只有使用異步的庫才會真正發揮它異步的優勢,當然有些時候因為App本身要求並因為App本身要求不是很高,如果不是阻塞特別嚴重的話,也不會有問題。另外使用coroutine模組進行非同步程式設計的時候,當把一個功能封裝到一個函數中時,在函數運行中,即使出現錯誤,如果沒有去捕捉的話也不會拋出,這在調試上顯得非常困難。