使用PySide应用程序实现异步函数执行

8
我有一个示例pyside演示程序,用来查看webkit浏览器与python通信... 在webkit中我有两个按钮。
  • 按钮1 - 点击后休眠10秒钟,然后打印一条消息

  • 按钮2 - 点击后立即打印一条消息。

当我点击按钮1时,整个应用程序会冻结并等待Python完成睡眠,这意味着我无法点击按钮2来做其他事情。如何在函数调用之间实现异步方法?

我的Python代码如下:

import sys,json
from time import sleep
from PySide.QtCore import *
from PySide.QtGui import *
from PySide.QtWebKit import QWebView, QWebSettings
from PySide.QtNetwork import QNetworkRequest
from PySide.QtCore import QObject, Slot, Signal

    html_str="""<!doctype>
        <html>

        <body>hello world
        <button id="button" >button1</button>
        <button id="button2" >button2</button>
        </body>
    </html>
    <script type="text/javascript">
    document.getElementById("button").onclick=function(){
    object.reply(" hello ");
    }
    document.getElementById("button2").onclick=function(){
    object.reply2(" hello ");
    }
    function data_from_js(msg){
        var tag=document.createElement('div');
        tag.innerHTML="message from python";
        document.body.appendChild(tag);
        alert(msg['name']);
    }
    </script>
    <style>
    body{
    border:solid black 1px;
    }
    </style>
    </doctype>"""
class Qbutton(QObject):
    from time import sleep
    def __init__(self):
        super(Qbutton,self).__init__()
    @Slot(str)
    def reply(self,recd):
        #r=QMessageBox.information(self,"Info",msg)
        msgBox = QMessageBox()
        sleep(10)
        msgBox.setText("python just said"+recd)
        msgBox.exec_()
        return "I am recieving pythonic data"
        #r=QMessageBox.question(self,title,recd,QMessageBox.Yes | QMessageBox.No)
    @Slot(str)
    def reply2(self,recd):
        msgBox = QMessageBox()
        msgBox.setText("python just said"+recd+ " another time")
        msgBox.exec_()
        return "I am recieving pythonic data"        
    @Slot(str)
    def send_tojs(self):
        pass


class adstar_gui(QWidget):
    def __init__(self):        
        super(adstar_gui,self).__init__()
        self.setWindowTitle("Adstar Wordlist Generator")
        self.setMaximumWidth(5000)
        self.setMaximumHeight(5000)
        self.setMinimumWidth(500)
        self.setMinimumHeight(500)
        self.show()
        print "Sample window"

    def closeEvent(self,event):
        self.closeEvent()
if __name__=="__main__":
    Qapp=QApplication(sys.argv)
    t=QWebView()
    t.setHtml(html_str)
    button=Qbutton()
    t.page().mainFrame().addToJavaScriptWindowObject("object",button)
    t.show()
    #t.page().mainFrame().evaluateJavaScript("data_from_js(%s);" % (json.dumps({'name':"My name is Junior"}) ))
    QCoreApplication.processEvents()
    #sys.exit(Qapp.exec_())
    Qapp.exec_()

问题

我如何在 webkit 上点击 button 1 并让 Python 在后台执行一些操作,而不必等待 button 1 函数完成(以便 button 2 函数无需等待)?

请使用此演示并对其进行改进...非常感谢

2个回答

3
这里有几个问题。首先,值得指出的是,当您单击button1时应用程序为什么会冻结:点击将导致Qt调用事件处理程序reply,并且在此处理程序返回之前,Qt无法处理其他事件(根据我的经验,所有窗口系统都是这样工作的)。因此,如果您在事件处理程序中放置了任何长时间运行的例程,您的应用程序将在该例程完成之前冻结。任何时候,事件处理程序需要的时间超过约0.05秒,用户都会注意到。
正如titusjan在回答中所指出的那样,让Qt在时间间隔之后执行一个函数非常容易。但我认为您的问题不在于如何处理简单的时间延迟,而是如何处理长时间运行的进程。在我的示例代码中,我用循环替换了您的10秒延迟,该循环计算10个一秒延迟,我认为这是您试图实现的更好的模型。
解决方案是在另一个线程中执行长时间进程。您有两个选择:QThreads和Python线程。它们都有效,但我总是尽可能使用Python线程。它们文档更好,并且稍微轻量级一些。将线程指定为后台线程有时可以使应用程序关闭变得更加简单。此外,将多线程程序转换为使用多进程更容易。在下面的示例代码中,我使用了Python线程。
然后出现了一个问题,应用程序如何知道二次线程何时完成?为此,您必须创建自定义Qt信号。您的辅助线程在完成工作时发出此信号,主应用程序连接一个槽以在发生这种情况时执行某些操作。如果要创建自定义Qt信号,则必须在QObject的子类中声明它,就像我在示例中所做的那样。
不用说,必须处理所有标准的多线程问题。
import sys
import json
import threading
from time import sleep
from PySide.QtCore import *
from PySide.QtGui import *
from PySide.QtWebKit import QWebView, QWebSettings
from PySide.QtNetwork import QNetworkRequest
from PySide.QtCore import QObject, Slot, Signal

html_str="""<!doctype>
        <html>

        <body>hello world
        <button id="button" >button1</button>
        <button id="button2" >button2</button>
        </body>
    </html>
    <script type="text/javascript">
    document.getElementById("button").onclick=function(){
    object.reply(" hello ");
    }
    document.getElementById("button2").onclick=function(){
    object.reply2(" hello ");
    }
    function data_from_js(msg){
        var tag=document.createElement('div');
        tag.innerHTML="message from python";
        document.body.appendChild(tag);
        alert(msg['name']);
    }
    </script>
    <style>
    body{
    border:solid black 1px;
    }
    </style>
    </doctype>"""

class Qbutton(QObject):
    def __init__(self):
        super(Qbutton,self).__init__()
        self.long_thread = LongPythonThread()
        self.long_thread.thread_finished.connect(self.reply2_finished)

    @Slot(str)
    def reply(self,recd):
        print("reply")
        t = threading.Thread(target=self.long_thread.long_thread, args=(recd,))
        t.daemon = True
        t.start()

    @Slot(str)
    def reply2(self,recd):
        print("reply2")
        msgBox = QMessageBox()
        msgBox.setText("python just said"+recd)
        msgBox.exec_()
        return "I am receiving pythonic data"

    @Slot(str)
    def reply2_finished(self, recd):
        print("reply2 finished")
        msgBox = QMessageBox()
        msgBox.setText("python just said"+recd+ " another time")
        msgBox.exec_()

    @Slot(str)
    def send_tojs(self):
        pass

class LongPythonThread(QObject):    
    thread_finished = Signal(str)

    def __init__(self):
        super(LongPythonThread,self).__init__()

    def long_thread(self, recd):
        for n in range(10):
            sleep(1.0)
            print("Delayed for {:d}s".format(n+1))
        self.thread_finished.emit(recd)

if __name__=="__main__":
    Qapp=QApplication(sys.argv)
    t=QWebView()
    t.setHtml(html_str)
    button=Qbutton()
    t.page().mainFrame().addToJavaScriptWindowObject("object",button)
    t.show()
    #t.page().mainFrame().evaluateJavaScript("data_from_js(%s);" % (json.dumps({'name':"My name is Junior"}) ))
    QCoreApplication.processEvents()
    #sys.exit(Qapp.exec_())
    Qapp.exec_()

我在发布问题后尝试了QThreads并且它起作用了。我一直在等待有人发布这样的答案。非常出色的工作! - repzero
只是一个小问题...你没有在类LongPythonThread中传递参数给super,这会抛出异常...但我已经在我的示例中添加了它...谢谢。 - repzero
这是Py2和Py3中super()函数的区别。我编写的代码适用于Python3,但您的修改使其同时适用于两者。 - Paul Cornelius

1

使用QTimer在一定时间段后执行信号。像这样:

import sys,json
from PySide.QtCore import *
from PySide.QtGui import *
from PySide.QtWebKit import QWebView, QWebSettings
from PySide.QtNetwork import QNetworkRequest
from PySide.QtCore import QObject, Slot, Signal, QTimer

html_str="""<!doctype>
        <html>

        <body>hello world
        <button id="button" >button1</button>
        <button id="button2" >button2</button>
        </body>
    </html>
    <script type="text/javascript">
    document.getElementById("button").onclick=function(){
    object.replyAfter10Seconds(" hello ");
    }
    document.getElementById("button2").onclick=function(){
    object.reply2(" hello ");
    }
    function data_from_js(msg){
        var tag=document.createElement('div');
        tag.innerHTML="message from python";
        document.body.appendChild(tag);
        alert(msg['name']);
    }
    </script>
    <style>
    body{
    border:solid black 1px;
    }
    </style>
    </doctype>"""


class Qbutton(QObject):
    def __init__(self):
        super(Qbutton,self).__init__()
        self.timer = QTimer()
        self.timer.setSingleShot(True)
        self.timer.setInterval(10 * 1000)
        self.timer.timeout.connect(self.reply)
    @Slot(str)
    def replyAfter10Seconds(self,recd):
        self._replyText = recd
        print "Started timer"
        self.timer.start()
    @Slot()
    def reply(self):
        #r=QMessageBox.information(self,"Info",msg)
        msgBox = QMessageBox()
        msgBox.setText("python just said"+self._replyText)
        msgBox.exec_()
        return "I am recieving pythonic data"
        #r=QMessageBox.question(self,title,recd,QMessageBox.Yes | QMessageBox.No)
    @Slot(str)
    def reply2(self,recd):
        msgBox = QMessageBox()
        msgBox.setText("python just said"+recd+ " another time")
        msgBox.exec_()
        return "I am recieving pythonic data"        
    @Slot(str)
    def send_tojs(self):
        pass


class adstar_gui(QWidget):
    def __init__(self):        
        super(adstar_gui,self).__init__()
        self.setWindowTitle("Adstar Wordlist Generator")
        self.setMaximumWidth(5000)
        self.setMaximumHeight(5000)
        self.setMinimumWidth(500)
        self.setMinimumHeight(500)
        self.show()
        print "Sample window"

    def closeEvent(self,event):
        self.closeEvent()
if __name__=="__main__":
    Qapp=QApplication(sys.argv)
    t=QWebView()
    t.setHtml(html_str)
    button=Qbutton()
    t.page().mainFrame().addToJavaScriptWindowObject("object",button)
    t.show()
    t.raise_()
    #t.page().mainFrame().evaluateJavaScript("data_from_js(%s);" % (json.dumps({'name':"My name is Junior"}) ))
    QCoreApplication.processEvents() # does nothing as long as App.exec_() hasn't statred.
    #sys.exit(Qapp.exec_())
    Qapp.exec_()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接