Python-Flask-SocketIO envía un mensaje desde el hilo: no siempre funciona
multithreading socket.io (2)
Logré resolver el problema al simular varias funciones de Python, lo que hace que Python use las funciones de eventlet en lugar de las nativas. De esta manera, los hilos de fondo funcionan bien con eventlet.
https://github.com/miguelgrinberg/Flask-SocketIO/blob/master/example/app.py#L30-L31
Estoy en la situación en la que recibo un mensaje del cliente. Dentro de la función que maneja esa solicitud (@ socketio.on) quiero llamar a una función donde se realiza un trabajo pesado. Esto no debería resultar en el bloqueo del hilo principal y se piensa que el cliente está informado una vez que se realiza el trabajo. Así empiezo un nuevo hilo.
Ahora me encuentro con un comportamiento realmente extraño: el mensaje nunca llega al cliente. Sin embargo, el código llega a ese punto en particular donde se envía el mensaje. Aún más sorprendente es el hecho de que si no ocurre nada en el hilo, excepto el mensaje que se envía al cliente, la respuesta llega al cliente.
Para resumir: si algo de computación intensiva ocurre antes de que se envíe el mensaje, no se está entregando, de lo contrario.
Como se dice here y here , el envío de mensajes desde un hilo a los clientes no es un problema en absoluto:
En todos los ejemplos mostrados hasta este punto, el servidor responde a un evento enviado por el cliente. Pero para algunas aplicaciones, el servidor debe ser el originador de un mensaje. Esto puede ser útil para enviar notificaciones a los clientes de eventos que se originaron en el servidor, por ejemplo, en un hilo de fondo.
Aquí hay un código de ejemplo. Cuando se eliminan los elementos cortantes de comentarios (#), el mensaje (''foo from thread'') no encuentra su camino al cliente, de lo contrario lo hace.
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)
Estoy usando Python 3.4.3, Flask 0.10.1, flask-socketio1.2, eventlet 0.17.4.
Esta muestra se puede copiar y pegar en un archivo .py y el comportamiento se puede reproducir instantáneamente.
¿Alguien puede explicar este extraño comportamiento?
Actualizar
Parece ser un error de eventlet. Si lo hago:
socketio = SocketIO(app, async_mode=''threading'')
Obliga a la aplicación a no utilizar eventlet aunque esté instalada.
Sin embargo, esta no es una solución aplicable para mí, ya que usar ''subprocesos'' como async_mode se niega a aceptar datos binarios. Cada vez que envío algunos datos binarios del cliente al servidor dice:
WebSocket transport not available. Install eventlet or gevent and gevent-websocket for improved performance.
La tercera opción, usar gevent como async_mode no funciona para mí, así como gevent no tiene soporte para Python 3 todavía.
¿Alguna otra sugerencia?
Tengo el mismo problema. Pero creo que me enteré de lo que importa.
Cuando inicie SocketIO con el siguiente código y cree una cadena como la suya, el cliente NO PUEDE recibir el mensaje emitido por el servidor.
socketio = SocketIO(app) socketio.run()
Descubrí que el flask_socketio ofrece una función llamada start_background_task del document .
Aquí está la descripción de la misma.
start_background_task (target, * args, ** kwargs)
Inicie una tarea en segundo plano utilizando el modelo asíncrono apropiado. Esta es una función de utilidad que las aplicaciones pueden usar para iniciar una tarea en segundo plano utilizando el método que es compatible con el modo asíncrono seleccionado.
Parámetros:
target - la función target a ejecutar. args - argumentos para pasar a la función. kwargs - argumentos de palabra clave para pasar a la función. Esta función devuelve un objeto compatible con la clase Thread en la biblioteca estándar de Python.
El método start () en este objeto ya es llamado por esta función.
Así que sustituyo mi código thread=threading(target=xxx)
con socketio.start_background_task(target=xxx)
luego socketio.run()
. El servidor se atasca en el subproceso cuando se ejecuta en él, lo que significa que la función start_background_task
solo se devolvió una vez finalizado el subproceso.
Luego trato de usar gunicorn para ejecutar mi servidor con gunicorn --worker-class eventlet -w 1 web:app -b 127.0.0.1:5000
¡Entonces todo funciona bien!
Así que vamos a start_background_task elegir una forma adecuada para iniciar un hilo.