Tornado 4.3
于2015年11月6日发布,该版本正式支持Python3.5
的async
/await
关键字,而且用旧版本CPython编译Tornado一样可使用这两个关键字,这无疑是一种进步。其次,这是最后一个支持Python2.6
和Python3.2
的版本了,在后续的版本了会移除对它们的兼容。如今网络上尚未Tornado4.3
的中文文档,因此为了让更多的朋友能接触并学习到它,我开始了这个翻译项目,但愿感兴趣的小伙伴能够一块儿参与翻译,项目地址是tornado-zh on Github,翻译好的文档在Read the Docs上直接能够看到。欢迎Issues or PR。html
Tornado中推荐使用协程写异步代码. 协程使用了Python的yield
关键字代替链式回调来将程序挂起和恢复执行(像在 gevent中出现的轻量级线程合做方式有时也被称为协程,可是在Tornado中全部的协程使用明确的上下文切换,并被称为异步函数).python
使用协程几乎像写同步代码同样简单, 而且不须要浪费额外的线程. 它们还经过减小上下文切换来 使并发编程更简单.git
例子:github
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.body
async
和await
Python 3.5引入了async
和await
关键字(使用这些关键字的函数也被称为"原生协程").从Tornado 4.3,你能够用它们代替yield
为基础的协程.只须要简单的使用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
await
关键字比yield
关键字功能要少一些.例如,在一个使用 yield
的协程中,你能够获得Futures
列表, 可是在原生协程中,你必须把列表用 tornado.gen.multi
包起来. 你也可使用 tornado.gen.convert_yielded
来把任何使用yield
工做的代码转换成使用await
的形式.网络
虽然原生协程没有明显依赖于特定框架(例如它们没有使用装饰器,例如tornado.gen.coroutine
或asyncio.coroutine
), 不是全部的协程都和其余的兼容. 有一个coroutine runner在第一个协程被调用的时候进行选择, 而后被全部用await
直接调用的协程共享. Tornado的协程执行者(coroutine runner)在设计上是多用途的,能够接受任何来自其余框架的awaitable对象;其余的协程运行时可能有不少限制(例如,asyncio
协程执行者不接受来自其余框架的协程).基于这些缘由,咱们推荐组合了多个框架的应用都使用Tornado的协程执行者来进行协程调度.为了能使用Tornado来调度执行asyncio的协程, 可使用tornado.platform.asyncio.to_asyncio_future
适配器.并发
包含了yield
关键字的函数是一个生成器(generator).全部的生成器都是异步的;当调用它们的时候,会返回一个生成器对象,而不是一个执行完的结果.@gen.coroutine
装饰器经过yield
表达式和生成器进行交流, 并且经过返回一个.Future
与协程的调用方进行交互.框架
下面是一个协程装饰器内部循环的简单版本:异步
# 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
对象执行完成, 而后"解开(unwraps)"这个Future
对象,并把结果做为yield
表达式的结果传回给生成器.大多数异步代码历来不会直接接触Future
类.除非 Future
当即经过异步函数返回给yield
表达式.async
协程通常不会抛出异常: 它们抛出的任何异常将被.Future
捕获直到它被获得.这意味着用正确的方式调用协程是重要的, 不然你可能有被忽略的错误:
@gen.coroutine def divide(x, y): return x / y def bad_call(): # 这里应该抛出一个 ZeroDivisionError 的异常, 但事实上并无 # 由于协程的调用方式是错误的. divide(1, 0)
几乎全部的状况下, 任何一个调用协程的函数都必须是协程它自身, 而且在调用的时候使用yield
关键字. 当你复写超类中的方法, 请参阅文档,看看协程是否支持(文档应该会写该方法"多是一个协程"或者"可能返回一个 Future
类 "):
@gen.coroutine def good_call(): # yield 将会解开 divide() 返回的 Future 而且抛出异常 yield divide(1, 0)
有时你可能想要对一个协程"一劳永逸"并且不等待它的结果. 在这种状况下,建议使用.IOLoop.spawn_callback
, 它使得.IOLoop
负责调用. 若是它失败了, .IOLoop
会在日志中把调用栈记录下来:
# IOLoop 将会捕获异常,而且在日志中打印栈记录. # 注意这不像是一个正常的调用, 由于咱们是经过 # IOLoop 调用的这个函数. IOLoop.current().spawn_callback(divide, 1, 0)
最后, 在程序顶层, 若是.IOLoop
还没有运行, 你能够启动.IOLoop
,执行协程,而后使用.IOLoop.run_sync
方法中止.IOLoop
. 这一般被用来启动面向批处理程序的main
函数:
# run_sync() 不接收参数,因此咱们必须把调用包在lambda函数中. IOLoop.current().run_sync(lambda: divide(1, 0))
为了使用回调代替.Future
与异步代码进行交互, 把调用包在.Task
类中. 这将为你添加一个回调参数而且返回一个能够yield的.Future
:
@gen.coroutine def call_task(): # 注意这里没有传进来some_function. # 这里会被Task翻译成 # some_function(other_args, callback=callback) yield gen.Task(some_function, other_args)
从协程调用阻塞函数最简单的方式是使用concurrent.futures.ThreadPoolExecutor
, 它将返回和协程兼容的Futures
:
thread_pool = ThreadPoolExecutor(4) @gen.coroutine def call_blocking(): yield thread_pool.submit(blocking_func, args)
协程装饰器能识别列表或者字典对象中各自的 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它更有用, 因此你能够在等待以前
执行其余操做:
@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()
协程的循环是棘手的, 由于在Python中没有办法在for
循环或者while
循环yield
迭代器,而且捕获yield的结果. 相反,你须要将循环条件从访问结果中分离出来, 下面是一个使用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
一般不使用协程. 相反,一个协程能够包含一个while True:
循环并使用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) # 开始计时. yield do_something() # 计时后运行. yield nxt # 等待计时结束.