【问题标题】:How does long-polling work in Tornado?Tornado 中的长轮询如何工作?
【发布时间】:2013-05-07 20:44:29
【问题描述】:

在Tornado的chat demo中,有这样一个方法:

@tornado.web.asynchronous
def post(self):
    cursor = self.get_argument("cursor", None)
    global_message_buffer.wait_for_messages(self.on_new_messages,
                                            cursor=cursor)

我对这个长轮询的东西还很陌生,虽然它说:

通过使用非阻塞网络 I/O,Tornado 可以扩展到数万个打开的连接...

我的理论是通过制作一个简单的应用程序:

import tornado.ioloop
import tornado.web
import time

class MainHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    def get(self):
        print("Start request")
        time.sleep(4)
        print("Okay done now")
        self.write("Howdy howdy howdy")
        self.finish()

application =  tornado.web.Application([
    (r'/', MainHandler),
])

如果我连续发出两个请求(即我打开两个浏览器窗口并快速刷新两者),我会看到:

Start request
Start request
Okay done now
Okay done now

相反,我明白了

Start request
Okay done now
Start request
Okay done now

这让我相信它在这种情况下实际上是阻塞的。为什么我的代码被阻塞了,我怎样才能得到一些代码来做我期望的事情?我在带有 i7 核心的 Windows 7 和带有两个核心的 linux Mint 13 机器上得到了相同的输出。

编辑:

我找到了一种方法 - 如果有人可以提供一种跨平台工作的方法(我不太担心性能,只是它是非阻塞的),我会接受这个答案。

【问题讨论】:

  • time.sleep 阻止 Tornado IOLoop,这会停止所有处理。您根本不需要多个线程(尽管您可能希望它们在生产中使用),只是不要睡觉。相反,向 IOLoop 添加超时:tornadoweb.org/en/stable/…
  • 请选择dano和john提供的正确答案。

标签: python asynchronous tornado


【解决方案1】:

将您的测试应用程序转换为不会阻塞 IOLoop 的表单的正确方法是这样的:

from tornado.ioloop import IOLoop
import tornado.web
from tornado import gen
import time

@gen.coroutine
def async_sleep(timeout):
    """ Sleep without blocking the IOLoop. """
    yield gen.Task(IOLoop.instance().add_timeout, time.time() + timeout)

class MainHandler(tornado.web.RequestHandler):
    @gen.coroutine
    def get(self):
        print("Start request")
        yield async_sleep(4)
        print("Okay done now")
        self.write("Howdy howdy howdy")
        self.finish()

if __name__ == "__main__":
    application =  tornado.web.Application([
        (r'/', MainHandler),
    ])
    application.listen(8888)
    IOLoop.instance().start()

不同之处在于将对time.sleep 的调用替换为不会阻塞IOLoop 的调用。 Tornado 旨在处理大量并发 I/O,而不需要多个线程/子进程,但如果您使用同步 API,它仍然会阻塞。为了让您的长轮询解决方案以您想要的方式处理并发,您必须确保没有长时间运行的调用阻塞。

【讨论】:

    【解决方案2】:

    原始问题中代码的问题在于,当您调用time.sleep(4) 时,您实际上将事件循环的执行阻塞了 4 秒。并且接受的答案也不能解决问题(恕我直言)。

    Tornado 中的异步服务依赖于信任。 Tornado 会在发生任何事情时调用您的函数,但它相信您会尽快将控制权交还给它。如果您使用 time.sleep() 阻止,则此信任被破坏 - Tornado 无法处理新连接。

    使用多线程只会隐藏错误;使用数千个线程运行 Tornado(因此您可以同时提供 1000 个连接)将非常低效。适当的方法是运行一个仅在 Tornado 内部阻塞的线程(在 select 或任何 Tornado 监听事件的方式) - 而不是在您的代码上(确切地说:never 在您的代码上) .

    正确的解决方案是在time.sleep() 之前从get(self) 返回(不调用self.finish()),如下所示:

    class MainHandler(tornado.web.RequestHandler):
        @tornado.web.asynchronous
        def get(self):
            print("Starting")
    

    您当然必须记住此请求仍处于打开状态,并稍后致电write()finish()

    我建议你看看chat demo。去掉身份验证后,您将获得一个非常好的异步长轮询服务器示例。

    【讨论】:

    • 我确实看过聊天演示,它的“真实生活”代码比我正在寻找的要多得多——甚至减少了。我意识到(现在)睡眠不是让它工作的正确方法......
    • 不幸的是,这样做 this 仍然会产生“开始”,然后阻塞直到第一个请求完全完成(至少据我所知)
    • 我同意聊天演示有点臃肿。 :) 我所做的是我删除了所有身份验证内容,只剩下一个非常小的应用程序,它清楚地显示了这些概念(我也将一些函数重命名为 -wait_for_messagesadd_to_waiters 和类似的)。关于第二条评论:不,它没有,这就是它的美妙之处。它一直接受新连接,同时保持旧连接打开(直到其他一些事件 - 例如另一个请求 - 关闭它们)。 first part of this Twisted tutorial 很好地解释了异步模式。
    【解决方案3】:

    自 Tornado 5.0 以来,asyncio 已自动启用,因此几乎只需将 time.sleep(4) 更改为 await asyncio.sleep(4) 并将 @tornado.web.asynchronous def get(self): 更改为 async def get(self): 即可解决问题。

    例子:

    import tornado.ioloop
    import tornado.web
    import asyncio
    
    class MainHandler(tornado.web.RequestHandler):
        async def get(self):
            print("Start request")
            await asyncio.sleep(4)
            print("Okay done now")
            self.write("Howdy howdy howdy")
            self.finish()
    
    app =  tornado.web.Application([
        (r'/', MainHandler),
    ])
    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()
    

    输出:

    Start request
    Start request
    Okay done now
    Okay done now
    

    来源:

    【讨论】:

      猜你喜欢
      • 2013-06-12
      • 1970-01-01
      • 2013-09-04
      • 2012-12-10
      • 2014-12-13
      • 1970-01-01
      • 1970-01-01
      • 2013-02-03
      • 1970-01-01
      相关资源
      最近更新 更多