Search code examples
pythonwebsockettornadoasyncsocket

how to correctly use async with tornado


in the documents of tornado is mentioned that gen.coroutine decorators are from older versions. newer should be used with aysnc. So far a I failed to convert that little code for Tornado 6.0.3.

import tornado.web
import tornado.websocket
import tornado.httpserver

from random import randint
from tornado import gen
from tornado.ioloop import IOLoop

from web_handlers import HandlerIndexPage
from web_handlers import HandlerWebSocket

msg = 'none'

@gen.coroutine
def generate_random_int():
    global msg
    while True:
        msg = str(randint(0, 100))
        print('generated:', msg)
        yield gen.sleep(1.0)

@gen.coroutine
def generate_message_to_sockets():
    global msg
    while True:
        print ('new messageToCon: ', msg)
        yield [con.write_message(msg) for con in HandlerWebSocket.connections]
        yield gen.sleep(1.0)


class webApplication(tornado.web.Application):
    def __init__(self):
        handlers = [
            (r'/', HandlerIndexPage),
            (r'/websocket', HandlerWebSocket)
        ]

        settings = {
            'template_path': 'templates'
        }
        tornado.web.Application.__init__(self, handlers, **settings)

if __name__ == '__main__':
    ws_app = webApplication()
    server = tornado.httpserver.HTTPServer(ws_app)
    port = 9090
    server.listen(port)
    print('websocket listening on port:'+ str(port))
    IOLoop.current().spawn_callback(generate_random_int)
    IOLoop.current().spawn_callback(generate_message_to_sockets)
    IOLoop.instance().start()

How would I correctly use async ?


Solution

  • In Tornado, coroutines are generators that can be plugged into Python2 whereas async functions are first-class citizens in Python3. They have different semantics (for instance, you can return from an async function, but not a coroutine). The code you would need to change would look like:

    ...
    
    import asyncio
    
    ...
    
    async def generate_random_int():
        global msg
        while True:
            msg = str(randint(0, 100))
            print('generated:', msg)
            await gen.sleep(1.0)
    
    async def generate_message_to_sockets():
        global msg
        while True:
            print ('new messageToCon: ', msg)
            futures = [con.write_message(msg) for con in HandlerWebSocket.connections]
            if futures:
                await asyncio.wait(futures)
            await gen.sleep(1.0)
    ...
    

    You can see an example similar to your use-case here: https://www.tornadoweb.org/en/stable/ioloop.html#ioloop-objects