Python - Flask-SocketIO отправить сообщение из потока: не всегда работает

Я в ситуации, когда получаю сообщение от клиента. Внутри функции, которая обрабатывает этот запрос (@socketio.on), я хочу вызвать функцию, где выполняется какая-то тяжелая работа. Это не должно приводить к блокировке основного потока, и считается, что клиент будет проинформирован после завершения работы. Таким образом, я запускаю новый поток.

Теперь я сталкиваюсь с действительно странным поведением: Сообщение никогда не доходит до клиента. Однако код достигает этого места, где отправляется сообщение. Еще более удивительным является тот факт, что если в потоке ничего не происходит, кроме сообщения, отправляемого клиенту, тогда ответ на самом деле находит свой путь к клиенту.

Подводя итог: Если что-то вычислительно интенсивно происходит до отправки сообщения, оно не доставляется, иначе оно есть.

Как говорится здесь и здесь, отправка сообщений от потока к клиентам не является проблемой вообще:

Во всех примерах, показанных до этого момента, сервер отвечает на событие, отправленное клиентом. Но для некоторых приложений сервер должен быть инициатором сообщения. Это может быть полезно для отправки уведомлений клиентам о событиях, возникших на сервере, например, в фоновом потоке.

Вот пример кода. При удалении комментариев (*) сообщение ('foo from thread') не находит своего пути к клиенту, в противном случае это делает.

from flask import Flask
from flask.ext.socketio import SocketIO, emit
app = Flask(__name__)
socketio = SocketIO(app)

from threading import Thread
import time 

@socketio.on('client command')
def response(data):
    thread = Thread(target = testThreadFunction)
    thread.daemon = True
    thread.start()

    emit('client response', ['foo'])

def testThreadFunction():
#   time.sleep(1)

    socketio.emit('client response', ['foo from thread'])

socketio.run(app)

Я использую Python 3.4.3, Flask 0.10.1, flask-socketio1.2, eventlet 0.17.4.

Этот образец может быть скопирован и вставлен в .py файл, и поведение может быть немедленно воспроизведено.

Может кто-нибудь объяснить это странное поведение?

Обновление

Кажется, это ошибка в eventlet. Если я это сделаю:

socketio = SocketIO(app, async_mode='threading')

Это заставляет приложение не использовать флажок, хотя он установлен.

Однако это не применимое решение для меня, как использование "threading", поскольку async_mode отказывается принимать двоичные данные. Каждый раз, когда я отправляю некоторые двоичные данные от клиента на сервер, он говорит:

WebSocket transport not available. Install eventlet or gevent and gevent-websocket for improved performance.

Третий вариант: использование gevent async_mode для меня не работает, а gevent еще не поддерживает python 3.

Итак, любые другие предложения?

Ответы

Ответ 2

У меня та же проблема. Но я думаю, что выяснил, в чем дело.

При запуске SocketIO со следующим кодом и создании потоков, подобных вашему, клиент НЕ МОЖЕТ получать сообщение, отправленное сервером.

socketio = SocketIO(app) socketio.run()

Я обнаружил, что flask_socketio предлагает функцию с именем start_background_task из документа.

Вот описание этого.

start_background_task (target, * args, ** kwargs)

Запустите фоновое задание, используя соответствующую асинхронную модель. Это служебная функция, которую приложения могут использовать для запуска фоновой задачи, используя метод, совместимый с выбранным асинхронным режимом.

Параметры:

target - целевая функция для выполнения. args - аргументы для передачи в функцию. kwargs - ключевые аргументы для передачи в функцию. Эта функция возвращает объект, совместимый с классом Thread в стандартной библиотеке Python.

Метод start() для этого объекта уже вызывается этой функцией.

Поэтому я заменяю свой код thread=threading(target=xxx) на socketio.start_background_task(target=xxx) затем socketio.run(). Сервер застревает в потоке при запуске, что означает, что функция start_background_task возвращается только после завершения потока.

Затем я пытаюсь использовать gunicorn для запуска моего сервера с gunicorn --worker-class eventlet -w 1 web:app -b 127.0.0.1:5000

Тогда все работает хорошо!

Итак, пусть start_background_task выберет правильный способ запуска потока.