烧瓶 + Gevent,monkey.patch_all() 打破烧瓶

时间:2021-02-13 13:30:33

标签: python flask pyqt5 gevent

我需要运行monkey.patch_all(),因为我的应用程序中有一个Flask 服务器与ValvePython 库相结合(我收到关于无法切换到其他线程的错误)但是我遇到了一个问题。 我已经在没有 ValvePython 的情况下对此进行了测试,以确保问题不会完全取决于此。

我如何启动服务器:

from gevent import monkey; monkey.patch_all();
if __name__ == "__main__":
    # Create PyQt5 app
    app = QApplication(sys.argv)

    # Flask server
    server = Server('Ryder Engine')

    # Create the custom window (The initialize function creates all the
    # server endpoints dynamically via the add_endpoint function
    window = RyderDisplay()
    window.initialize(server)

    # Run Server
    threading.Thread(target=server.run, daemon=True).start()

    # Start the app
    sys.exit(app.exec())

我的服务器类:

import socket
from flask import Flask, Response, request
from gevent.pywsgi import WSGIServer

class EndpointAction(object):
    def __init__(self, action):
        self.action = action
        self.response = Response(status=200, headers={})

    def __call__(self, *args):
        self.action(request.get_json())
        return self.response

class Server(object):
    def __init__(self, name):
        self.app = Flask(name)

    def run(self, port=9520):
        http_server = WSGIServer(('0.0.0.0', port), self.app)
        http_server.serve_forever()

    def add_endpoint(self, endpoint=None, endpoint_name=None, handler=None):
        self.app.add_url_rule(endpoint, endpoint_name, EndpointAction(handler), methods=['POST'])

绑定端点之一的 PyQt5 应用程序的主页。这是在 Window 对象内实例化的。其余的通过 json 配置文件通过 HomeConfigurationParser 类进行实例化。

class Home(object):
    # Class constructor
    def __init__(self, window, server : Server):
        self._window = window
        self._client = Client()
        self._server = server

        self._client.subscribeToRyderEngine()
        server.add_endpoint('/status', 'status', self.newStatus)

    # UI Elements
    def create_ui(self, path):
        # Initialize
        path =  path + '/config.json'
        self._fps, self._ui = HomeConfigurationParser.parse(self._window, self._client, self._server, path)

        # Refresher
        self._timer = QTimer()
        self._timer.timeout.connect(self.update)
        self._timer.start(1000 / self._fps)

    def newStatus(self, request):
        self._status = request

    def update(self):
        # Update UI
        for elem in self._ui:
            elem.update(self._status)
        # Reset
        if self._status is not None:
            self._status = None

我的问题是,通过运行 monkey.path_all() 服务器不再处理请求,换句话说,它基本上忽略了所有 add_endpoint 函数调用。服务器端点必须在运行时添加我不能通过函数上面的@ 直接在代码中添加它们。

为什么会发生这种情况,我该如何解决?

编辑:添加了更多代码位。服务器与 PyQt5 接口并行运行。服务器用于接收数据,然后相应地更新PyQt5接口

1 个答案:

答案 0 :(得分:1)

Flask 源代码 (here) 中的 add_url_rule 有一个 @setupmethod 装饰器,表示“在处理第一个请求后忽略我”。 (这是 @app.before_first_request 机制的关键部分。)

如果您在一个线程中启动 Flask,然后从另一个线程调用 add_endpoint,则您的调用线程会与应用的第一个请求竞争。线程安全也存在​​一些严重的问题,这些问题会引起从 Flask 主线程外部调用对 Flask 内部产生副作用的方法。

在您的位置上,我会重新安排以确保所有 add_endpoint 调用都发生在 server.run 启动应用程序之前。

您可能仍然对monkeypatching 有问题,但我会先解决这个问题。