常见问题¶
为什么这个例子没有 time.sleep()
并行运行?¶
许多人第一次进入 Tornado 的同时,看起来是这样的:
class BadExampleHandler(RequestHandler):
def get(self):
for i in range(5):
print(i)
time.sleep(1)
同时获取此处理程序两次,您将看到第二个五秒倒计时在第一个倒计时完成之前不会开始。原因是 time.sleep
是一个 舞台调度 功能:不允许控件返回到 IOLoop
以便运行其他处理程序。
当然, time.sleep
实际上只是这些示例中的一个占位符,重点是显示当处理程序中的某些内容变慢时会发生什么。无论真正的代码在做什么,为了实现并发性,必须用非阻塞等价物替换阻塞代码。这意味着三件事之一:
Find a coroutine-friendly equivalent. 为了
time.sleep
使用tornado.gen.sleep
(或)asyncio.sleep
):class CoroutineSleepHandler(RequestHandler): async def get(self): for i in range(5): print(i) await gen.sleep(1)
当此选项可用时,通常是最佳方法。见 Tornado wiki 用于链接到可能有用的异步库。
Find a callback-based equivalent. 与第一个选项类似,基于回调的库可用于许多任务,尽管它们比为协程设计的库稍微复杂一些。使基于回调的函数适应未来:
class CoroutineTimeoutHandler(RequestHandler): async def get(self): io_loop = IOLoop.current() for i in range(5): print(i) f = tornado.concurrent.Future() do_something_with_callback(f.set_result) result = await f
再一次, Tornado wiki 有助于找到合适的库。
在另一个线程上运行阻塞代码。 当异步库不可用时,
concurrent.futures.ThreadPoolExecutor
可用于在另一个线程上运行任何阻塞代码。这是一个通用的解决方案,可用于任何阻塞函数,无论是否存在异步对等项::class ThreadPoolHandler(RequestHandler): async def get(self): for i in range(5): print(i) await IOLoop.current().run_in_executor(None, time.sleep, 1)
见 Asynchronous I/O Tornado用户指南的第章,了解有关阻塞和异步函数的更多信息。
我的代码是异步的。为什么它不能在两个浏览器选项卡中并行运行?¶
即使处理程序是异步的并且是非阻塞的,验证这一点也非常困难。浏览器将识别出您正试图在两个不同的选项卡中加载同一页,并将第二个请求延迟到第一个请求完成为止。要解决此问题并查看服务器实际上是并行工作的,请执行以下两项操作之一:
添加一些东西到你的网址,使它们独一无二。而不是
http://localhost:8888
在两个选项卡中,加载http://localhost:8888/?x=1
合而为一http://localhost:8888/?x=2
在另一个。使用两种不同的浏览器。例如,即使在chrome标签中加载相同的URL,火狐也可以加载一个URL。