#python #tornado
#python #tornado
Вопрос:
Мое решение ThreadPoolExecutor / gen.coroutine (tornado v4.x) для обхода блокировки веб-сервера больше не работает с tornado версии 6.x .
Некоторое время назад я начал разрабатывать браузерную онлайн-игру, используя веб-сервер Tornado (v4.x) и websockets. Всякий раз, когда ожидается ввод данных пользователем, игра отправляет вопрос клиенту и ожидает ответа. Раньше я использовал gen.coroutine и ThreadPoolExecutor, чтобы сделать эту задачу неблокирующей. Теперь, когда я начал рефакторинг игры, она не работает с tornado v6.x, и задача снова блокирует сервер. Я искал возможные решения, но пока мне не удалось заставить ее снова работать. Мне непонятно, как изменить мой существующий код, чтобы он снова не блокировался.
server.py:
class PlayerWebSocket(tornado.websocket.WebSocketHandler):
executor = ThreadPoolExecutor(max_workers=15)
@run_on_executor
def on_message(self,message):
params = message.split(':')
self.player.callbacks[int(params[0])]=params[1]
if __name__ == '__main__':
application = Application()
application.listen(9999)
tornado.ioloop.IOLoop.instance().start()
player.py:
@gen.coroutine
def send(self, message):
self.socket.write_message(message)
def create_choice(self, id, choices):
d = {}
d['id'] = id
d['choices']=choices
self.choice[d['id']]=d
self.send('update',self)
while not d['id'] in self.callbacks:
pass
del self.choice[d['id']]
return self.callbacks[d['id']]
Всякий раз, когда нужно сделать выбор, функция create_choice создает dict со списком (вариантами) и идентификатором и сохраняет его в players self.callbacks. После этого она просто остается в цикле while, пока функция websocket.on_message не поместит полученный ответ (который выглядит так: id: Choice_id, например, 1:12838732) в dict обратного вызова.
Ответ №1:
WebSocketHandler.write_message
Метод не является потокобезопасным, поэтому его можно вызвать только из потока IOLoop, а не из ThreadPoolExecutor (это всегда было правдой, но иногда могло показаться, что он все равно работает).
Самый простой способ исправить этот код — сохранить IOLoop.current()
в глобальной переменной из основного потока ( current()
функция обращается к локальной переменной потока, поэтому вы не можете вызвать ее из пула потоков) и использовать ioloop.add_callback(self.socket.write_message, message)
(и удалить @gen.coroutine
из send
— бесполезно создавать сопрограммы функций, если они не содержат yield
выражений).