4

在 Tornado 的聊天演示中,它有一个这样的方法:

@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 盒子上得到了相同的输出。

编辑:

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

4

3 回答 3

6

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

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

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

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

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

你当然必须记住,这个请求仍然是开放的,稍后再调用它write()finish()

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

于 2013-05-17T07:28:45.107 回答
6

将您的测试应用程序转换为不会阻止 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,它仍然会阻塞。为了让您的长轮询解决方案以您想要的方式处理并发,您必须确保没有长时间运行的调用阻塞。

于 2014-05-26T19:13:45.557 回答
1

自 Tornado 5.0 以来,asyncio 自动启用,因此几乎只需更改time.sleep(4)toawait asyncio.sleep(4)@tornado.web.asynchronous def get(self):to 即可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

资料来源:

于 2018-04-20T09:44:55.597 回答