Frequently Asked Questions

Why isn’t this example with time.sleep() running in parallel?

许多人首次尝试使用Tornado的并发性是这样的:

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

同时两次获取此处理程序,您将看到第二个五秒倒计时直到第一个完全结束后才开始. 原因是time.sleep是一个阻塞函数:它不允许控件返回IOLoop以便可以运行其他处理程序.

当然,在这些示例中, time.sleep实际上只是一个占位符,目的是显示当处理程序中的某些东西变慢时会发生什么. 无论实际代码在做什么,要实现并发性,阻塞代码都必须替换为非阻塞等效项. 这意味着三件事之一:

  1. 找到一个协程友好的等效项. 对于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. 查找基于回调的等效项. 与第一个选项类似,基于回调的库可用于许多任务,尽管它们的使用比为协程设计的库稍微复杂一些. 使基于回调的函数适应未来的需求:

    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)
    

有关阻塞和异步功能的更多信息,请参见《 Tornado用户指南》中的" 异步I / O"一章.

My code is asynchronous. Why is it not running in parallel in two browser tabs?

即使当处理程序是异步且非阻塞的时,验证这一点也可能非常棘手. 浏览器将识别出您正在尝试在两个不同的选项卡中加载同一页面,并将第二个请求延迟到第一个请求完成为止. 要变通解决此问题并查看服务器实际上是否在并行工作,请执行以下两项操作之一:

  • 在您的网址中添加一些内容,以使其具有唯一性. 而不是http://localhost:8888在两个标签,负载http://localhost:8888/?x=1在一个和http://localhost:8888/?x=2的其他.

  • 使用两种不同的浏览器. 例如,即使在Chrome选项卡中加载了相同的URL,Firefox也将能够加载该URL.