从QML到Python的异步调用,带有回调



从QML,我想:

  1. 调用 Python 插槽。
  2. 传递回调。
  3. 在槽完成后运行该回调。

我试过这个:

  1. 注册上下文属性 ( Service
  2. 呼叫Service.request("data", function (response) { console.log(response) }
  3. 在 Python 中,该函数作为QtQml.QJSValue接收
  4. 该函数在经过一些昂贵的操作后在单独的线程中调用

但是,该函数仅在有时起作用,大多数时候根本没有效果或使Python解释器崩溃。如果我删除对time.sleep(1)的调用,它更有可能产生结果。

有什么想法吗?

这是上述的非工作实现

主.qml

import QtQuick 2.3
import "application.js" as App

Rectangle {
    id: appWindow
    width: 200
    height: 200
    Component.onCompleted: App.onLoad()
}

main.py

import sys
import time
import threading
from PyQt5 import QtCore, QtGui, QtQml, QtQuick

class Service(QtCore.QObject):
    def __init__(self, parent=None):
        super(Service, self).__init__(parent)
    @QtCore.pyqtSlot(str, str, QtCore.QVariant, QtQml.QJSValue)
    def request(self, verb, endpoint, data, cb):
        """Expensive call"""
        print verb, endpoint, data
        self.cb = cb
        def thread():
            time.sleep(1)
            event = QtCore.QEvent(1000)
            event.return_value = "expensive result"
            QtGui.QGuiApplication.postEvent(self, event)
        worker = threading.Thread(target=thread)
        worker.daemon = False
        worker.start()
        self.worker = worker
    def event(self, event):
        if event.type() == 1000:
            self.cb.call([event.return_value])
        return super(Service, self).event(event)

app = QtGui.QGuiApplication(sys.argv)
view = QtQuick.QQuickView()
context = view.rootContext()
service = Service()
context.setContextProperty("Service", service)
view.setSource(QtCore.QUrl("main.qml"))
view.show()
app.exec_()

应用.js

"use strict";
/*global print, Service*/

function onLoad() {
    Service.request("POST", "/endpoint", {"data": "value"}, function (reply) {
        print(reply);
        print(reply);
        print(reply);
    });
    print("request() was made");
}

实现是从这里
改编的https://github.com/ben-github/PyQt5-QML-CallbackFunction

最好
马库斯

我找到了一种也有效的替代方法。

区别在于:

  1. Python 注册一个新类型,而不是设置上下文属性
  2. Python不是从Python调用Javascript,而是发出信号

这对我来说似乎更干净,因为Javascript永远不必进入Python。

主.qml

import QtQuick 2.0
import "application.js" as App

Rectangle {
    id: appWindow
    width: 200
    height: 200
    Component.onCompleted: App.onLoad()
}

main.py

import sys
import time
import threading
from PyQt5 import QtCore, QtGui, QtQml, QtQuick

class MockHTTPRequest(QtCore.QObject):
    requested = QtCore.pyqtSignal(QtCore.QVariant)
    @QtCore.pyqtSlot(str, str, QtCore.QVariant)
    def request(self, verb, endpoint, data):
        """Expensive call"""
        print verb, endpoint, data
        def thread():
            time.sleep(1)
            self.requested.emit("expensive result")
        threading.Thread(target=thread).start()
app = QtGui.QGuiApplication(sys.argv)
view = QtQuick.QQuickView()
context = view.rootContext()
QtQml.qmlRegisterType(MockHTTPRequest, 'Service', 1, 0, 'MockHTTPRequest')
view.setSource(QtCore.QUrl("main.qml"))
view.show()
app.exec_()

应用.js

"use strict";
/*global print, Service, Qt, appWindow*/

function MockHTTPRequest() {
    return Qt.createQmlObject("import Service 1.0; MockHTTPRequest {}",
                              appWindow, "MockHTTPRequest");
}
function onLoad() {
    var xhr = new MockHTTPRequest();
    xhr.requested.connect(function (reply) {
        print(reply);
    });
    xhr.request("POST", "/endpoint", {"data": "value"});
    print("request() was made");
}

文档中没有迹象表明QJSValue是线程安全的。此页指示可重入类或线程安全的类在文档中被标记为可重入类。但是,页面上没有提到QJSValue的单词线程。

因此,我建议您确保仅从主线程调用回调。显然,你仍然希望将长时间运行的任务放在一个线程中,所以我建议使用类似QCoreApplication.postEvent()的东西将事件从你的Python线程发送到主线程,然后主线程将调用你的回调函数。

注意:我已经在这里包装了对 PyQt4 QCoreApplication.postEvent的调用。如果你需要帮助理解如何使用QCoreApplication.postEvent方法,你也可以调整它以与 PyQt5 配合使用。

相关内容

  • 没有找到相关文章

最新更新