常见问题

为什么这个例子没有 time.sleep() 并行运行?

许多人第一次进入 Tornado 的同时,看起来是这样的:

class BadExampleHandler(RequestHandler):
    def get(self):
        for i in range(5):
            print(i)
            time.sleep(1)

同时获取此处理程序两次,您将看到第二个五秒倒计时在第一个倒计时完成之前不会开始。原因是 time.sleep 是一个 舞台调度 功能:不允许控件返回到 IOLoop 以便运行其他处理程序。

当然, time.sleep 实际上只是这些示例中的一个占位符,重点是显示当处理程序中的某些内容变慢时会发生什么。无论真正的代码在做什么,为了实现并发性,必须用非阻塞等价物替换阻塞代码。这意味着三件事之一:

  1. 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 用于链接到可能有用的异步库。

  2. 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 有助于找到合适的库。

  3. 在另一个线程上运行阻塞代码。 当异步库不可用时, 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。