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

人气:514 发布:2022-10-16 标签: python multithreading socket.io flask-socketio gevent

问题描述

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

I am in the situation where I receive a message from the client. Within the function that handles that request (@socketio.on) I want to call a function where some heavy work is done. This should not result in blocking the main thread and the client is thought to be informed once the work is done. Thus I start a new thread.

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

Now I encounter a really strange behavior: The message never reaches the client. However, the code reaches that particular spot where the message is sent. Even more surprising is the fact that if there is nothing happening in the thread except for the message being sent to the client then the answer actually finds its way to the client.

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

To sum it up: If something computationally intensive happens before the message is sent it is not being delivered, otherwise it is.

就像说这里和here,从线程向客户端发送消息不是问题全部:

Like it is said here and here, sending messages from a thread to the clients is not a problem at all:

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

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') 不会找到通往客户端的路,否则会找到.

Here is a sample code. When removing the commenting sharps (#) the message ('foo from thread') does not find its way to the client, otherwise it does.

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.

I am using Python 3.4.3, Flask 0.10.1, flask-socketio1.2, eventlet 0.17.4.

此示例可以复制并粘贴到 .py 文件中,并且可以立即重现行为.

This sample can be copied and pasted in a .py file and the behavior can be instantly reproduced.

有人可以解释这种奇怪的行为吗?

Can somebody explain this strange behavior?

更新

好像是eventlet的一个bug.如果我这样做:

It seems to be a bug of eventlet. If I do:

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

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

It forces the application not to use eventlet although it is installed.

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

However, this is not an applicable solution for me as using 'threading' as async_mode refuses to accept binary data. Every time I send some binary data from the client to the server it says:

WebSocket 传输不可用.安装 eventlet 或 gevent 和 gevent-websocket 以提高性能.

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

The third option, using gevent as the async_mode does not work for me as well as gevent does not have support for python 3 yet.

还有其他建议吗?

推荐答案

我设法通过对几个 Python 函数进行猴子补丁来解决这个问题,这导致 Python 使用 eventlet 函数而不是本机函数.这样后台线程可以很好地与 eventlet 配合使用.

I managed to resolve the issue by monkeypatching several Python functions which causes Python to use the eventlet functions instead of the native ones. This way background threads work fine with eventlet.

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

350