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

Python - Flask-SocketIO send message from thread: not always working

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

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

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

正如所说here and here,从线程向客户端发送消息完全不是问题:

In all the examples shown until this point the server responds to an event sent by the client. But for some applications, the server needs to be the originator of a message. This can be useful to send notifications to clients of events that originated in the server, for example in a background thread.

这是一个示例代码。删除注释符号 (#) 时,消息 ('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的bug。如果我这样做:

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

它强制应用程序不使用 eventlet,尽管它已安装。

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

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

第三个选项,使用 gevent 作为 async_mode 对我不起作用,而且 gevent 还不支持 python 3。

还有其他建议吗?

我设法通过 monkeypatching 几个 Python 函数解决了这个问题,这导致 Python 使用 eventlet 函数而不是本机函数。这样后台线程可以很好地与 eventlet 一起工作。

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

我也遇到了同样的问题。 但我想我发现了问题所在。

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

socketio.run()

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

这是它的描述。

start_background_task(target, *args, **kwargs)

Start a background task using the appropriate async model. This is a utility function that applications can use to start a background task using the method that is compatible with the selected async mode.

Parameters:

target – the target function to execute. args – arguments to pass to the function. kwargs – keyword arguments to pass to the function. This function returns an object compatible with the Thread class in the Python standard library.

The start() method on this object is already called by this function.

所以我将我的代码 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选择一个合适的线程启动方式。

您 运行 遇到的问题是由于 eventlet 和 gevent(socket.io 的两种线程模式)不支持多处理造成的。所以这不是错误,而是实现方式。为了让它工作,你可以使用 async_mode=threading,或者你可以使用 monkey-patch evenlet 来启用后台线程。

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