Запустить действия на главном цикле Торнадо, после того, как он запустится - PullRequest
0 голосов
/ 26 апреля 2019

Я создаю веб-сервер торфа python3, который может прослушивать брокер MQTT и всякий раз, когда прослушивает новое сообщение от него, транслирует его в подключенные браузеры через веб-сокеты. Однако, похоже, что Tornado не нравится вызовы его API из потока, отличного от IOLoop.current (), и я не могу найти другое решение ...

Я уже пытался написать код. Я поместил весь клиент MQTT (в данном случае он называется клиентом PMCU) в отдельный поток, который зацикливается и прослушивает уведомления MQTT.

def on_pmcu_data(data):
    for websocket_client in websocket_clients:
        print("Sending websocket message")
        websocket_client.write_message(data)  # Here it stuck!
        print("Sent")

class WebSocketHandler(tornado.websocket.WebSocketHandler):
    def open(self):
        websocket_clients.append(self)

    def on_close(self):
        websocket_clients.remove(self)

def make_app():
    return tornado.web.Application([
        (r'/ws', WebSocketHandler)
    ])

if __name__ == "__main__":
    main_loop = IOLoop().current()

    pmcu_client = PMCUClient(on_pmcu_data)
    threading.Thread(target=lambda: pmcu_client.listen("5.4.3.2")).start()

    app = make_app()
    app.listen(8080)
    main_loop.start()

Однако, как я уже сказал, кажется, что вызовы Tornado API вне блоков IOLoop.current (): приведенный выше код печатает только Sending websocket message.

Мое намерение - запустить websocket_client.write_message(data) в IOLoop.current() цикле событий. Но похоже, что функция IOLoop.current().spawn_callback(lambda: websocket_client.write_message(data)) не работает после запуска IOLoop.current(). Как мне этого добиться?

Я знаю, что у меня огромное недопонимание IOLoop, asyncio, от которого он зависит, и асинхронности python3.

1 Ответ

0 голосов
/ 28 апреля 2019

on_pmcu_data вызывается в отдельном потоке, но веб-сокет управляется циклом событий Торнадо.Вы не можете писать в веб-сокет из потока, если у вас нет доступа к циклу событий.

Вам нужно будет попросить IOLoop записать данные в веб-сокеты.

Решение 1:

Для простых случаев, если вы не хотите сильно менять код, вы можете сделать это:

if __name__ == "__main__":
    main_loop = IOLoop().current()

    on_pmcu_data_callback = lambda data: main_loop.add_callback(on_pmcu_data, data)

    pmcu_client = PMCUClient(on_pmcu_data_callback)

    ...

Это должно решить вашу проблему.


Решение 2:

В более сложных случаях вы можете передать класс main_loop в PMCUClient изатем используйте add_callback (или spawn_callback) для запуска on_pmcu_data.

Пример:

if __name__ == "__main__":
    main_loop = IOLoop().current()

    pmcu_client = PMCUClient(on_pmcu_data, main_loop) # also pass the main loop

    ...

Затем в PMCUCLient class:

class PMCUClient:
    def __init__(self, on_pmcu_data, main_loop):
        ...
        self.main_loop = main_loop

    def lister(...):
        ...
        self.main_loop.add_callback(self.on_pmcu_data, data)
...