14

I am trying to learn tornado coroutines, but I have error using below code.

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'

Code:

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()
klis87
  • 478
  • 1
  • 4
  • 18
  • What are you trying to do at the line `x = yield 'test'`? –  Nov 20 '14 at 15:22
  • that is just an example, I tried to yield result of a function too - result is the same, this line is just to test coroutines – klis87 Nov 20 '14 at 15:23

2 Answers2

31

As Lutz Horn pointed out, the tornado.coroutine decorator requires that you yield only Future objects or certain containers containing Future objects. So trying to yield a str will raise an error. I think the piece you're missing is that any place inside of a coroutine where you want to call yield something(), something must either also be a coroutine, or return a Future. For example, you could fix your example like this:

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+

Or even this (though generally you shouldn't do it this way):

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

Of course, these are contrived examples; since we're not actually doing anything asynchronous in do_test, there's no reason to make it a coroutine. Normally you'd be doing some kind of asynchronous I/O in there. For example:

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+
dano
  • 91,354
  • 19
  • 222
  • 219
  • Thanks for great answer, I added time.sleep at the beginning of function do_test, and everything works as in synchronous code, I get self.render executed after sleep(x) is finished, I thought that maybe this function is performed in the background, it is not the case though... What is the benefit of using coroutines then? Maybe another user could open connection in the same time before sleep of previous user is finished? – klis87 Nov 20 '14 at 16:07
  • 2
    @user3575996 Using `yield function()` will make the execution of the coroutine wait until `function()` has completed. However, it will *not* block the tornado I/O loop, so if other requests from clients come in, those could be handled in the meantime. However, you can't use `time.sleep()` to test this, because it's a blocking function. You need to use a non-blocking version of sleep. See [this answer](http://stackoverflow.com/a/23876402/2073595) for an example. – dano Nov 20 '14 at 16:14
  • Thanks now I finally understand everything:) – klis87 Nov 20 '14 at 16:17
  • Can you adapt the `raise Return ...` examples to Python 3.3+ ? The guide says that one may use `return` in up to date Python versions: http://www.tornadoweb.org/en/stable/guide/coroutines.html#python-3-5-async-and-await seemingly making the `raise something` way of doing things obsolete. – Zelphir Kaltstahl Nov 11 '16 at 18:27
  • @Zelphir Python 2.7 still needs to use `raise Return(...)`, which is still a pretty significant user base. I'll update the examples to reflect both ways to do it. – dano Nov 11 '16 at 18:36
3

From the documentation:

Most asynchronous functions in Tornado return a Future; yielding this object returns its result.

You can also yield a list or dict of Futures, which will be started at the same time and run in parallel; a list or dict of results will be returned when they are all finished:

The string "test" is not a Future. Try to yield one.

  • 2
    Let's say I want to yield result of database query, lets say function get_all_users() does this for me, could you please give me an example as tornado examples use some build-in classes like AsyncHTTPClient(), how to implement your own – klis87 Nov 20 '14 at 15:29