上下文: 在Python中,主线程产生第二个进程(使用多处理模块),然后启动GUI(使用PyQt4)。此时主线程将阻塞,直到GUI关闭。第二个进程总是处理,理想情况下应该以异步方式向GUI中的特定插槽发出信号。
问题: Python和PyQt4中有哪些方法/工具可以实现这些以及如何实现?最好采用软中断方式而不是轮询。
抽象地说,我能想到的解决方案是"工具/处理程序"在主线程中实例化,该主线程从GUI实例获取可用的插槽,并与来自第二个进程的抓取信号连接,假设我为此工具提供了一些预期或硬编码的信息。这可以实例化为第3个进程/线程。
答案 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监视进程。