如何从不同的进程发信号通知GUI中的插槽?

时间:2014-11-04 22:37:10

标签: python pyqt multiprocessing signals pyside

上下文: 在Python中,主线程产生第二个进程(使用多处理模块),然后启动GUI(使用PyQt4)。此时主线程将阻塞,直到GUI关闭。第二个进程总是处理,理想情况下应该以异步方式向GUI中的特定插槽发出信号。

问题: Python和PyQt4中有哪些方法/工具可以实现这些以及如何实现?最好采用软中断方式而不是轮询。

抽象地说,我能想到的解决方案是"工具/处理程序"在主线程中实例化,该主线程从GUI实例获取可用的插槽,并与来自第二个进程的抓取信号连接,假设我为此工具提供了一些预期或硬编码的信息。这可以实例化为第3个进程/线程。

4 个答案:

答案 0 :(得分:15)

这是一个示例Qt应用程序,演示了从子进程向母进程中的插槽发送信号。我不确定这是正确的做法,但它确实有效。

我将流程区分为母亲孩子,因为 parent 一词在Qt上下文中被使用。
母进程有两个线程。母进程的主线程通过multiprocessing.Queue向子进程发送数据。子进程通过multiprocessing.Pipe发送处理数据和要发送到母进程的第二个线程的信号的签名。母进程的第二个线程实际上发出信号。

Python 2.X,PyQt4:

from multiprocessing import Process, Queue, Pipe
from threading import Thread
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *

class Emitter(QObject, Thread):

    def __init__(self, transport, parent=None):
        QObject.__init__(self,parent)
        Thread.__init__(self)
        self.transport = transport

    def _emit(self, signature, args=None):
        if args:
            self.emit(SIGNAL(signature), args)
        else:
            self.emit(SIGNAL(signature))

    def run(self):
        while True:
            try:
                signature = self.transport.recv()
            except EOFError:
                break
            else:
                self._emit(*signature)

class Form(QDialog):

    def __init__(self, queue, emitter, parent=None):
        super(Form,self).__init__(parent)
        self.data_to_child = queue
        self.emitter = emitter
        self.emitter.daemon = True
        self.emitter.start()
        self.browser = QTextBrowser()
        self.lineedit = QLineEdit('Type text and press <Enter>')
        self.lineedit.selectAll()
        layout = QVBoxLayout()
        layout.addWidget(self.browser)
        layout.addWidget(self.lineedit)
        self.setLayout(layout)
        self.lineedit.setFocus()
        self.setWindowTitle('Upper')
        self.connect(self.lineedit,SIGNAL('returnPressed()'),self.to_child)
        self.connect(self.emitter,SIGNAL('data(PyQt_PyObject)'), self.updateUI)

    def to_child(self):
        self.data_to_child.put(unicode(self.lineedit.text()))
        self.lineedit.clear()

    def updateUI(self, text):
        text = text[0]
        self.browser.append(text)

class ChildProc(Process):

    def __init__(self, transport, queue, daemon=True):
        Process.__init__(self)
        self.daemon = daemon
        self.transport = transport
        self.data_from_mother = queue

    def emit_to_mother(self, signature, args=None):
        signature = (signature, )
        if args:
            signature += (args, )
        self.transport.send(signature)

    def run(self):
        while True:
            text = self.data_from_mother.get()
            self.emit_to_mother('data(PyQt_PyObject)', (text.upper(),))

if __name__ == '__main__':

    app = QApplication(sys.argv)
    mother_pipe, child_pipe = Pipe()
    queue = Queue()
    emitter = Emitter(mother_pipe)
    form = Form(queue, emitter)
    ChildProc(child_pipe, queue).start()
    form.show()
    app.exec_()

同样方便Python 3.X,PySide:

from multiprocessing import Process, Queue, Pipe
from threading import Thread

from PySide import QtGui, QtCore

class Emitter(QtCore.QObject, Thread):

    def __init__(self, transport, parent=None):
        QtCore.QObject.__init__(self, parent)
        Thread.__init__(self)
        self.transport = transport

    def _emit(self, signature, args=None):
        if args:
            self.emit(QtCore.SIGNAL(signature), args)
        else:
            self.emit(QtCore.SIGNAL(signature))

    def run(self):
        while True:
            try:
                signature = self.transport.recv()
            except EOFError:
                break
            else:
                self._emit(*signature)

class Form(QtGui.QDialog):

    def __init__(self, queue, emitter, parent=None):
        super().__init__(parent)
        self.data_to_child = queue
        self.emitter = emitter
        self.emitter.daemon = True
        self.emitter.start()
        self.browser = QtGui.QTextBrowser()
        self.lineedit = QtGui.QLineEdit('Type text and press <Enter>')
        self.lineedit.selectAll()
        layout = QtGui.QVBoxLayout()
        layout.addWidget(self.browser)
        layout.addWidget(self.lineedit)
        self.setLayout(layout)
        self.lineedit.setFocus()
        self.setWindowTitle('Upper')
        self.lineedit.returnPressed.connect(self.to_child)
        self.connect(self.emitter, QtCore.SIGNAL('data(PyObject)'), self.updateUI)

    def to_child(self):
        self.data_to_child.put(self.lineedit.text())
        self.lineedit.clear()

    def updateUI(self, text):
        self.browser.append(text[0])

class ChildProc(Process):

    def __init__(self, transport, queue, daemon=True):
        Process.__init__(self)
        self.daemon = daemon
        self.transport = transport
        self.data_from_mother = queue

    def emit_to_mother(self, signature, args=None):
        signature = (signature, )
        if args:
            signature += (args, )
        self.transport.send(signature)

    def run(self):
        while True:
            text = self.data_from_mother.get()
            self.emit_to_mother('data(PyQt_PyObject)', (text.upper(),))

if __name__ == '__main__':

    app = QApplication(sys.argv)
    mother_pipe, child_pipe = Pipe()
    queue = Queue()
    emitter = Emitter(mother_pipe)
    form = Form(queue, emitter)
    ChildProc(child_pipe, queue).start()
    form.show()
    app.exec_()

答案 1 :(得分:2)

首先应该看看信号/插槽如何只在一个Python进程中工作:

如果只有一个正在运行的QThread,他们只是直接调用插槽。

如果信号是在另一个线程上发出的,它必须找到信号的目标线程,并在该线程的线程队列中发送消息/发布事件。然后,该线程将在适当的时候处理消息/事件并调用信号。

因此,内部总是涉及某种轮询,重要的是轮询是非阻塞的。

multiprocessing创建的流程可以通过Pipes进行通信,每个方面为您提供两个connections

poll的{​​{1}}函数是非阻塞的,因此我会定期用Connection进行轮询,然后相应地发出信号。

另一种解决方案可能是从线程模块(或QThread)获得QTimer,专门用于等待来自Thread的具有队列Queue功能的新消息。有关详细信息,请参阅multiprocessing的管道和队列部分。

这是一个示例,在另一个get中启动Qt GUI以及在Process上监听的Thread,并在某个消息上关闭GUI,然后终止该过程。

Connection

答案 2 :(得分:0)

一个非常有趣的话题。我想有一个在线程之间工作的信号是一个非常有用的东西。如何基于套接字创建自定义信号? 我还没有对此进行测试,但这是我通过一些快速调查收集的内容:

class CrossThreadSignal(QObject):
    signal = pyqtSignal(object)
    def __init__(self, parent=None):
        super(QObject, self).__init__(parent)
        self.msgq = deque()
        self.read_sck, self.write_sck = socket.socketpair()
        self.notifier = QSocketNotifier(
                           self.read_sck.fileno(), 
                           QtCore.QSocketNotifier.Read
                        )
        self.notifier.activated.connect(self.recv)

    def recv(self):
        self.read_sck.recv(1)
        self.signal.emit(self.msgq.popleft())

    def input(self, message):
        self.msgq.append(message)
        self.write_sck.send('s')

可能只是让你走上正轨。

答案 3 :(得分:0)

我在C ++中遇到了同样的问题。从QApplication中,我生成了一个Service对象。该对象创建了Gui Widget,但它不是它的父节点(父节点是QApplication)。为了从服务小部件控制GuiWidget,我像往常一样使用信号和插槽,它按预期工作。 注意:GuiWidget的线程和其中一个服务是不同的。该服务是QObject的子类。

如果您需要多进程信号/插槽机制,请尝试使用Apache Thrift或使用生成2个QProcess对象的Qt监视进程。