Search code examples
pythonasynchronouswebsocketwebservertornado

How to run an infinite loop asynchronously in a websocket with Tornado


I am running a web server on a raspberry pi which is logging temperatures etc. I am using websockets in Tornado to communicate with my client. I want the client to be able to control when the server is sending data over the socket.

My thought was that when client connects and says it's ready, the server will start a loop where it logs temps every second. But I need this loop to run asynchronously. This is where I get in trouble. I tried to follow an example, but I cant get it to run properly.

class TemperatureSocketHandler(tornado.websocket.WebSocketHandler):

    @gen.coroutine
    def async_func(self):
        num = 0
        while(self.sending):
            num = num + 1
            temp = self.sense.get_temperature()
            yield self.write_message(str(temp))
            gen.sleep(1)

    def open(self):
        print("Temperature socket opened")
        self.sense = SenseHat()
        self.sense.clear()
        self.sending = False

    def on_message(self, message):
        if(message == "START"):
            self.sending = True
        if(message == "STOP"):
            self.sending = False

        tornado.ioloop.IOLoop.current().spawn_callback(self.async_func(self))

But I get an error here when I run this:

ERROR:tornado.application:Exception in callback functools.partial(<function wrap.<locals>.null_wrapper at 0x75159858>)
Traceback (most recent call last):
  File "/home/pi/.local/lib/python3.5/site-packages/tornado/ioloop.py", line 605, in _run_callback
    ret = callback()
  File "/home/pi/.local/lib/python3.5/site-packages/tornado/stack_context.py", line 277, in null_wrapper
    return fn(*args, **kwargs)
TypeError: 'Future' object is not callable

Solution

  • You have to use IOLoop.add_future() since async_func() returns a Future (it is decorated as a coroutine!).

    Also, you should add the future, when you receive the start message, not on any message:

    def on_message(self, message):
            if(message == "START"):
                self.sending = True
                tornado.ioloop.IOLoop.current().add_future(
                    self.async_func(self), lambda f: self.close())
            if(message == "STOP"):
                self.sending = False