Python - Flask-SocketIO从线程发送消息:并不总是工作

时间:2016-01-03 20:29:26

标签: python multithreading socket.io gevent flask-socketio

我正处于从客户端收到消息的情况。在处理该请求的函数内(@socketio.on)我想调用一个完成一些繁重工作的函数。这不应该导致阻塞主线程,并且一旦工作完成就认为客户端被通知。因此,我开始一个新的线程。

现在我遇到了一个非常奇怪的行为: 消息永远不会到达客户端。但是,代码到达发送消息的特定位置。 更令人惊讶的是,如果除了将消息发送到客户端之外,线程中没有任何事情发生,那么答案实际上会找到客户端。

总结一下: 如果在发送消息之前发生计算密集的事情,那么它就不会被传递,否则就是。

就像说herehere一样,将消息从线程发送到客户端根本不是问题:

  

在此之前显示的所有示例中,服务器都会响应客户端发送的事件。但对于某些应用程序,服务器需要是消息的发起者。这对于向客户端发送通知在服务器中发起的事件(例如在后台线程中)非常有用。

以下是示例代码。删除评论分片(#)时,消息(' foo来自线程')找不到客户端,否则就会发现。

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')

虽然安装了应用程序,但它强制应用程序不使用eventlet。

然而,对于我来说,这不是一个适用的解决方案,因为使用'线程' async_mode拒绝接受二进制数据。每次我从客户端向服务器发送一些二进制数据时都会说:

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

使用gevent作为async_mode的第三个选项对我来说不起作用,gevent也不支持python 3。

那么还有其他建议吗?

3 个答案:

答案 0 :(得分:8)

我设法通过monkeypatching几个Python函数来解决这个问题,这些函数导致Python使用eventlet函数而不是本机函数。这样背景线程可以与eventlet一起使用。

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

答案 1 :(得分:0)

我有同样的问题。 但我想我发现了什么是重要的。

使用以下代码启动SocketIO并创建与您类似的线程时,客户端无法接收服务器发出的消息。

socketio = SocketIO(app) socketio.run()

我发现flask_socketio从document提供了一个名为start_background_task的函数。

以下是对它的描述。

  

start_background_task(target,* args,** kwargs)

     

使用适当的异步模型启动后台任务。这是一个实用程序函数,应用程序可以使用与所选异步模式兼容的方法启动后台任务。

     

参数:

     

target - 要执行的目标函数。 args - 传递给函数的参数。   kwargs - 传递给函数的关键字参数。此函数返回与Python标准库中的Thread类兼容的对象。

     

此函数已调用此对象的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选择一种正确的方式来启动一个线程。

答案 2 :(得分:0)

您遇到的问题是由于eventlet和gevent(socket.io的两种线程化模式)不支持多处理这一事实引起的。因此,这不是错误,而是实现的方式。为了使其正常工作,可以使用async_mode = threading,也可以使用Monkey-patch Evenlet启用后台线程的使用。

socketio = SocketIO(app, async_mode='eventlet')
import eventlet
eventlet.monkey_patch()