【问题标题】:Asynchronous call from QML to Python with callback从 QML 到 Python 的异步调用,带有回调
【发布时间】:2015-01-20 18:43:45
【问题描述】:

来自 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) 的调用,则更有可能产生结果。

有什么想法吗?

这是上述的非工作实现

ma​​in.qml

import QtQuick 2.3

import "application.js" as App


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

ma​​in.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_()

application.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

最好的,
马库斯

【问题讨论】:

    标签: python pyqt qml qt5 pyqt5


    【解决方案1】:

    我找到了另一种可行的方法。

    区别在于:

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

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

    ma​​in.qml

    import QtQuick 2.0
    
    import "application.js" as App
    
    
    Rectangle {
        id: appWindow
        width: 200
        height: 200
        Component.onCompleted: App.onLoad()
    }
    

    ma​​in.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_()
    

    application.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");
    }
    

    【讨论】:

      【解决方案2】:

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

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

      注意:我已经为 PyQt4 here 打包了对 QCoreApplication.postEvent 的调用。如果您需要帮助了解如何使用 QCoreApplication.postEvent 方法,您也可以将其调整为与 PyQt5 一起使用。

      【讨论】:

      • 大概率是这样,没想到!
      • 我试了一下并更新了示例,您介意再看一下吗?它还没有完全削减它,当使用信号而不是事件时它也不起作用,而且我的想法很新鲜......
      • 目前我手头没有 PyQt5。 event() 会被调用吗? (能不能插入打印语句检查)
      • 是的,将 self.cb.call([event.return_value]) 替换为 print event.return_value 确实会按预期打印值。然而,原始版本会导致 Python 崩溃。
      • 查看您的代码基于什么(以及来自创建者的邮件列表帖子),看起来您需要在保存回调时包装回调。例如self.cb = QJSValue(cb) 以避免段错误。希望这与线程安全更改相结合可以阻止段错误。
      猜你喜欢
      • 2013-09-12
      • 2016-09-14
      • 2018-04-29
      • 1970-01-01
      • 2018-07-21
      • 2020-04-14
      • 1970-01-01
      • 2016-10-24
      • 1970-01-01
      相关资源
      最近更新 更多