异步调用与回调

异步调用与回调

问题描述:

从QML到Python,我想:异步调用与回调

  1. 呼叫一个Python插槽。
  2. 传递一个回调。
  3. 插槽完成后是否运行该回调。

我已经试过这样:

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

但是,该函数只有一个效果有时,大部分时间根本不会或崩溃Python解释器。如果我删除对time.sleep(1)的呼叫,则更可能产生结果。

任何想法?

这里的上述

main.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_() 

的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

最佳改编,
马库斯

没有从文档表明QJSValue是线程安全的。 This page表示可重入或线程安全的类在文档中标记为这样。但是,QJSValue页面上没有提及该字的线程。

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

注:我已将PyQt4 here的呼叫打包为QCoreApplication.postEvent。如果您需要帮助了解如何使用QCoreApplication.postEvent方法,则可以调整它以使用PyQt5。

+0

这是最有可能的是,甚至没有考虑这个! – 2014-11-23 09:03:40

+0

我给出了这个例子,并且更新了这个例子,你会介意再看一次吗?它还没有完全切断它,在使用信号而不是事件时它也不起作用,并且我刚刚从创意中脱颖而出。 – 2014-11-23 09:45:14

+0

我目前没有PyQt5。事件()被调用吗? (你可以插入一个print语句来检查) – 2014-11-23 10:14:59

我发现了一种可行的替代方法。

的区别是:

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

这似乎吸尘器我,因为JavaScript永远不必进入Python。

main.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_() 

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