web-dev-qa-db-fra.com

Python - Flask-SocketIO envoyer un message à partir du thread: ne fonctionne pas toujours

Je suis dans la situation où je reçois un message du client. Dans la fonction qui gère cette demande (@ socketio.on), je souhaite appeler une fonction nécessitant un travail lourd. Cela ne devrait pas avoir pour effet de bloquer le thread principal et on pense que le client est informé une fois le travail terminé. Ainsi je commence un nouveau fil.

Maintenant, je rencontre un comportement vraiment étrange: Le message n'arrive jamais au client. Cependant, le code parvient à cet endroit particulier où le message est envoyé . Plus surprenant encore est le fait que si rien ne se passe dans le fil sauf le message envoyé au client, la réponse parvient effectivement au destinataire. client.

Pour résumer: Si quelque chose de très intensif en informatique se produit avant que le message ne soit envoyé, il n'est pas remis, sinon c'est le cas.

Comme il est dit ici et ici , envoyer des messages d'un fil aux clients n'est pas un problème du tout:

Dans tous les exemples présentés jusqu'à présent, le serveur répond à un événement envoyé par le client. Mais pour certaines applications, le serveur doit être à l'origine du message. Cela peut être utile pour envoyer des notifications aux clients d’événements provenant du serveur, par exemple dans un fil d’arrière-plan.

Voici un exemple de code. Lors de la suppression des dièses de commentaire (#), le message ("foo from thread") ne parvient pas au client, sinon il le fait.

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)

J'utilise Python 3.4.3, Flask 0.10.1, flask-socketio1.2, Eventlet 0.17.4.

Cet exemple peut être copié et collé dans un fichier .py et le comportement peut être reproduit instantanément.

Quelqu'un peut-il expliquer ce comportement étrange?

Mettre à jour

Cela semble être un bug d'Eventlet. Si je fais:

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

Cela force l'application à ne pas utiliser eventlet bien qu'il soit installé.

Cependant, ce n'est pas une solution applicable pour moi, car l'utilisation de 'threading' dans la mesure où async_mode refuse d'accepter des données binaires. Chaque fois que j'envoie des données binaires du client au serveur, cela dit:

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

La troisième option, qui utilise gevent en tant que async_mode, ne fonctionne pas pour moi et gevent ne prend pas encore en charge python 3.

Donc, d'autres suggestions?

18
Schnodderbalken

J'ai réussi à résoudre le problème en monkeypatching plusieurs fonctions Python, ce qui a amené Python à utiliser les fonctions Eventlet au lieu des fonctions natives. De cette façon, les threads d'arrière-plan fonctionnent bien avec eventlet.

https://github.com/miguelgrinberg/Flask-SocketIO/blob/master/example/app.py#L30-L31

7
Schnodderbalken

J'ai le même problème ... Mais je pense avoir découvert ce qui importait.

Lorsque vous démarrez SocketIO avec le code suivant et créez le thread comme le vôtre, le client NE PEUT PAS recevoir le message qui a été émis par le serveur.

socketio = SocketIO(app) socketio.run()

Je découvre que flask_socketio propose une fonction nommée start_background_task à partir de document .

En voici la description.

start_background_task (target, * args, ** kwargs)

Démarrez une tâche en arrière-plan en utilisant le modèle async approprié. Il s'agit d'une fonction utilitaire que les applications peuvent utiliser pour démarrer une tâche en arrière-plan à l'aide de la méthode compatible avec le mode async sélectionné. 

Paramètres:

cible - la fonction cible à exécuter. args - arguments à transmettre à la fonction . kwargs - arguments de mots clés à transmettre à la fonction. Cette fonction retourne un objet compatible avec la classe Thread de la bibliothèque standard Python.

La méthode start () sur cet objet est déjà appelée par cette fonction.

Je remplace donc mon code thread=threading(target=xxx) par socketio.start_background_task(target=xxx) puis socketio.run(). Le serveur reste bloqué dans le thread lorsqu'il y est exécuté, ce qui signifie que la fonction start_background_task n'est retournée qu'après la fin du thread.

Ensuite, j'essaie d'utiliser gunicorn pour exécuter mon serveur avec gunicorn --worker-class eventlet -w 1 web:app -b 127.0.0.1:5000

Alors tout fonctionne bien!

Laissez donc start_background_task choisir une méthode appropriée pour démarrer un thread.

0
殷明军