Tornado协程

14

我正在尝试学习tornado协程,但是我在使用以下代码时出现了错误。

Traceback (most recent call last):
  File "D:\projekty\tornado\env\lib\site-packages\tornado\web.py", line 1334, in _execute
    result = yield result
  File "D:\projekty\tornado\env\lib\site-packages\tornado\gen.py", line 628, in run
    value = future.result()
  File "D:\projekty\tornado\env\lib\site-packages\tornado\concurrent.py", line 109, in result
    raise_exc_info(self._exc_info)
  File "D:\projekty\tornado\env\lib\site-packages\tornado\gen.py", line 631, in run
    yielded = self.gen.throw(*sys.exc_info())
  File "index.py", line 20, in get
    x = yield 'test'
  File "D:\projekty\tornado\env\lib\site-packages\tornado\gen.py", line 628, in run
    value = future.result()
  File "D:\projekty\tornado\env\lib\site-packages\tornado\concurrent.py", line 111, in result
    raise self._exception
BadYieldError: yielded unknown object 'test'

代码:

from tornado.ioloop import IOLoop
from tornado.web import RequestHandler, Application, url
from tornado import gen

class HelloHandler(RequestHandler):
    @gen.coroutine
    def get(self):
        x = yield 'test'
        self.render('hello.html')


def make_app():
    return Application(
        [url(r"/", HelloHandler)], 
        debug = True
    )

def main():
    app = make_app()
    app.listen(8888)
    IOLoop.instance().start()

main()

你在 x = yield 'test' 这行代码中尝试做什么? - user1907906
这只是一个例子,我也尝试了函数的结果 - 结果是相同的,这行代码只是为了测试协程。 - klis87
2个回答

31

正如Lutz Horn所指出的,tornado.coroutine装饰器要求你只能yield Future对象或包含Future对象的某些容器。因此,尝试yield一个str将引发错误。我认为你缺少的部分是,在协程中任何调用yield something()的地方,something必须也是协程或返回一个Future。例如,你可以像这样修复你的示例:

from tornado.gen import Return

class HelloHandler(RequestHandler):
    @gen.coroutine
    def get(self):
        x = yield self.do_test()
        self.render('hello.html')

    @gen.coroutine
    def do_test(self):
        raise Return('test')
        # return 'test' # Python 3.3+

甚至可以这样做(尽管通常不应该这样做):

class HelloHandler(RequestHandler):
    @gen.coroutine
    def get(self):
        x = yield self.do_test()
        self.render('hello.html')

    def do_test(self):
        fut = Future()
        fut.set_result("test")
        return fut
当然,这些都是人为制造的例子;因为我们实际上并没有在do_test中执行任何异步操作,所以没有理由将其转换成协程。通常你会在里面执行某种异步I/O。例如:
class HelloHandler(RequestHandler):
    @gen.coroutine
    def get(self):
        x = yield self.do_test()
        self.render('hello.html')

    @gen.coroutine
    def do_test(self):
        http_client = AsyncHTTPClient()
        out = yield http_client.fetch("someurl.com") # fetch is a coroutine
        raise Return(out.body)
        # return out.body # Python 3.3+

感谢您的出色回答,我在函数do_test的开头添加了time.sleep,并且一切都像同步代码一样工作,我得到了在sleep(x)完成后执行self.render的结果。我曾经认为这个函数是在后台执行的,但事实并非如此...那么使用协程的好处是什么呢?也许另一个用户可以在前一个用户的睡眠结束之前同时打开连接? - klis87
2
使用 yield function() 将使协程的执行等待直到 function() 完成。但是,它不会阻塞tornado I/O循环,因此如果来自客户端的其他请求进来,这些请求可以在此期间处理。但是,您不能使用 time.sleep() 进行测试,因为它是一个阻塞函数。您需要使用非阻塞版本的sleep。请参见此答案以获取示例。 - dano
谢谢,现在我终于明白了一切 :) - klis87
你能将 raise Return ... 的例子适应于 Python 3.3+ 吗?该指南表示在最新的 Python 版本中可以使用 return:http://www.tornadoweb.org/en/stable/guide/coroutines.html#python-3-5-async-and-await,似乎使得使用 raise something 的方式已经过时了。 - Zelphir Kaltstahl
@Zelphir Python 2.7仍需要使用raise Return(...),这仍然是相当大的用户群。我会更新示例以反映两种方法。 - dano

3

从文档中得知:

Tornado中的大多数异步函数都返回一个Future对象;通过yield这个对象可以获取其结果。

你也可以yield一组Future构成的列表或字典,它们将同时启动并并行运行;当它们全部结束时,将返回一个结果列表或字典:

字符串"test"不是Future。请尝试yield一个。


2
假设我想要返回数据库查询的结果,比如说函数get_all_users()可以帮我实现这个功能,你能否给我一个例子呢?因为tornado的例子使用了一些内置类,比如AsyncHTTPClient(),所以我想知道如何实现自己的函数。 - klis87

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接