Qt(PyQt)事件循环中的xmlrpc?

Posted

技术标签:

【中文标题】Qt(PyQt)事件循环中的xmlrpc?【英文标题】:xmlprc inside Qt's (PyQt) event loop? 【发布时间】:2019-03-27 16:37:13 【问题描述】:

我想使用xmlprc 服务器和 Qt 应用程序,在这种情况下是 Qt Web 引擎,因此 xmlrpc 客户端可以向它发送命令。我必须与现有客户合作。

这里有两个事件循环。运行 Qt 的 exec_() 是阻塞的,运行 xmlrpc 的 serve_forever() 是阻塞的。

我首先想知道您是否知道 Python 的 xmlrpc 库,它与 Qt 的事件循环很好地集成。所以一切都可以开箱即用。


我没有找到,所以我尝试在一个线程中启动 rpc 服务器。它基本上可以工作,但它不能与 Qt 小部件交互。我们该怎么做?

下面是 pyqt5==5.12 和 PyQtWebEngine。

from PyQt5.QtCore import QUrl from PyQt5.QtCore import QThread
from PyQt5.QtWebEngineWidgets import QWebEngineView
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWidgets import QPushButton
from PyQt5.QtWidgets import QVBoxLayout
from PyQt5.QtWidgets import QWidget

from xmlrpc.server import SimpleXMLRPCServer

# Qt
URL_START = "http://ddg.gg"

app = QApplication([])
window = QWidget()
layout = QVBoxLayout()

webview = QWebEngineView()
webview.setUrl(QUrl(URL_START))

minibuffer = QWebEngineView()
mb_prompt = """
<html>
<div> hello minibuffer </div>
</html>
"""
minibuffer.setHtml(mb_prompt)

layout.addWidget(webview)
layout.addWidget(minibuffer)

window.setWindowTitle("My browser")
window.setLayout(layout)
window.show()

# xmlrpc
def hello(name):
    return "hello " + name

def set_minibuffer(name):
    return minibuffer.setHtml(mb_prompt.replace("minibuffer", name))


class RPCThread(QThread):
    def run(self):
        # sleep a little bit to make sure QApplication is running.
        self.sleep(1)
        print("--- starting server…")
        self.rpcserver = SimpleXMLRPCServer(("localhost", 8282))
        self.rpcserver.register_function(hello)
        self.rpcserver.register_function(set_minibuffer)

        self.rpcserver.serve_forever()

class RPCWidget(QWidget):
    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        self.thread = RPCThread(self)
        self.thread.start()

rpcwidget = RPCWidget()
rpcwidget.show()

# Qt main loop.
print("--- Qt loop")
app.exec_()

来自客户:

from xmlrpc.client import ServerProxy
client = ServerProxy("http://localhost:8282"
print(client.hello("me"))  # OK
# Trying to set the web engine's html content:
print(client.set_minibuffer("me")  # fails

这失败了

--- starting server…
127.0.0.1 - - [27/Mar/2019 16:52:01] "POST /RPC2 HTTP/1.1" 200 -
Received signal 11 SEGV_MAPERR 000000000000
#0 0x7f9b5bf3e8bf <unknown>
#1 0x7f9b5bf3ecbb <unknown>
#2 0x7f9b5bf3f33e <unknown>
#3 0x7f9b6667ef20 <unknown>
#4 0x7f9b5b9f3515 <unknown>
#5 0x7f9b5b9f37eb <unknown>
#6 0x7f9b5b9f3b79 <unknown>
#7 0x7f9b5a4731c0 QtWebEngineCore::WebContentsAdapter::setContent()
#8 0x7f9b610c7bf5 QWebEnginePage::setHtml()
#9 0x7f9b612fb824 meth_QWebEngineView_setHtml
#10 0x0000005030d5 <unknown>
#11 0x000000506859 _PyEval_EvalFrameDefault
#12 0x000000504c28 <unknown>
#13 0x00000058644b <unknown>
#14 0x00000059ebbe PyObject_Call
#15 0x000000507c17 _PyEval_EvalFrameDefault
[…]

【问题讨论】:

【参考方案1】:

您有 2 个错误:

您不能也不能直接从另一个线程修改 GUI,在您的情况下,set_minibuffer() 在 SimpleXMLRPCServer 所在的线程中执行。相反,您必须通过pyqtSignalQMetaObject::invokeMethod、自定义QEventsQTimer.singleShot(0, ...) 间接执行此操作。

我不是 xmlrpc 专家,但在 Why can't xmlrpc client append item to list accessable via xmlrpc server procedure? 中,它说您必须将 SimpleXMLRPCServer 对象传递给构造函数 allow_none = True

综合以上情况,解决办法是:

from functools import partial

# ...

from PyQt5.QtCore import QTimer

# ...

def set_minibuffer(name):
    html = mb_prompt.replace("minibuffer", name)
    wrapper = partial(minibuffer.setHtml, html)
    QTimer.singleShot(0, wrapper)
    return html


class RPCThread(QThread):
    def run(self):
        # sleep a little bit to make sure QApplication is running.
        self.sleep(1)
        print("--- starting server…")
        self.rpcserver = SimpleXMLRPCServer(("localhost", 8282), allow_none=True)
        self.rpcserver.register_function(hello)
        self.rpcserver.register_function(set_minibuffer)

        self.rpcserver.serve_forever()

# ...

【讨论】:

现在可以使用 O_o 来消化文档。谢谢。

以上是关于Qt(PyQt)事件循环中的xmlrpc?的主要内容,如果未能解决你的问题,请参考以下文章

进入Qt事件循环后如何自动执行方法?

python和PyQt5中的Quamash QventLoop“RuntimeError:没有运行事件循环”错误

QT中的线程与事件循环理解

QCoreApplication::exec: 事件循环已经在运行 - 调用另一个 PyQt5 文件

PyQT5 与 matplotlib 图,事件循环已经在运行

了解 Qt 多线程和事件循环