我正处于从客户端收到消息的情况。在处理该请求的函数内(@socketio.on)我想调用一个完成一些繁重工作的函数。这不应该导致阻塞主线程,并且一旦工作完成就认为客户端被通知。因此,我开始一个新的线程。
现在我遇到了一个非常奇怪的行为: 消息永远不会到达客户端。但是,代码到达发送消息的特定位置。 更令人惊讶的是,如果除了将消息发送到客户端之外,线程中没有任何事情发生,那么答案实际上会找到客户端。
总结一下: 如果在发送消息之前发生计算密集的事情,那么它就不会被传递,否则就是。
就像说here和here一样,将消息从线程发送到客户端根本不是问题:
在此之前显示的所有示例中,服务器都会响应客户端发送的事件。但对于某些应用程序,服务器需要是消息的发起者。这对于向客户端发送通知在服务器中发起的事件(例如在后台线程中)非常有用。
以下是示例代码。删除评论分片(#)时,消息(' 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。
那么还有其他建议吗?
答案 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()