关于多线程:Python-Flask-SocketIO从线程发送消息:并非始终有效

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

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

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

把它们加起来:
如果在发送消息之前发生了一些计算量大的事情,那么它就不会被传递。

就像在这里和这里所说的,将消息从线程发送到客户端根本不是问题:

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")找不到到达客户端的方式,否则会到达。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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的错误。如果我做:

1
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。

还有其他建议吗?


我设法通过猴子补丁几个Python函数来解决此问题,这使Python使用eventlet函数代替了本机函数。这样,后台线程可以与eventlet正常工作。

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


我也有同样的问题。
但是我想我发现了问题所在。

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

socketio = SocketIO(app)
socketio.run()

我发现flask_socketio从文档中提供了一个名为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选择适当的方式启动线程。