28

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

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

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

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

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

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

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。

那么还有其他建议吗?

4

3 回答 3

16

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

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

于 2016-01-04T19:17:22.950 回答
6

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

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

socketio.run()

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

这是它的描述。

start_background_task(目标,*args,**kwargs)

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

参数:

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

此对象上的 start() 方法已被此函数调用。

thread=threading(target=xxx)所以我用socketio.start_background_task(target=xxx)then替换我的代码socketio.run()。服务器在遇到线程时会卡在线程中,这意味着该函数start_background_task只有在线程完成后才返回。

然后我尝试使用 gunicorn 来运行我的服务器gunicorn --worker-class eventlet -w 1 web:app -b 127.0.0.1:5000

然后一切正常!

所以让 start_background_task 选择一个合适的方式来启动一个线程。

于 2018-03-21T16:06:03.467 回答
2

您遇到的问题是由于 eventlet 和 gevent(socket.io 的两种线程模式)不支持多处理造成的。所以这不是一个错误,而是实现的方式。为了使其工作,您可以使用 async_mode=threading,或者您可以猴子补丁 evenlet 以启用后台线程的使用。

socketio = SocketIO(app, async_mode='eventlet')
import eventlet
eventlet.monkey_patch()
于 2020-11-23T14:24:43.627 回答