Qthread is catching up after "pause" - python

I've made an application with pyside2 (qml).
The purpose is to read and present the values comming from an arduino to a chart.
The data is send every second over the serial connection to the pc.
To achieve this I had to create an QThread for not freezing up the UI.
Used explenation here.
In the UI I've a button to start/pause the app.
The application is almost working as expected:
Only when I pause the app and say after a minute or so I resume the app (QThread) I got a lot of data gathered from the Arduino when this should not be happening.
I expect as long as the thread is "pausing" it won't read data that I don't need.
How do I fix it so it won't read the data when the thread in on "pause" ?
main.py
from PySide2.QtCore import Signal, Slot, QObject, QThread
from PySide2.QtQml import QQmlApplicationEngine
from PySide2.QtWidgets import QApplication
class Worker(QThread):
data = Signal(object)
def __init__(self, ctrl):
QThread.__init__(self)
self.ctrl = ctrl
def run(self):
while True:
while self.ctrl["running"]:
self.data.emit(arduino.read_arduino())
else:
time.sleep(0)
if self.ctrl["break"]:
break
class MainWindow(QObject):
def __init__(self):
QObject.__init__(self)
self.thread = QThread()
self.ctrl = {"break": False, "running": True}
self.worker = Worker(self.ctrl)
#Slot()
def start_measurement(self):
if not self.thread.isRunning():
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.run)
self.worker.data.connect(self.handle_arduino_raw_data)
self.thread.start()
else:
self.ctrl["running"] = True
if self.ctrl["running"] = True:
self.ctrl["running"] = False
#Slot(str)
def handle_arduino_raw_data(self, data):
# handle the data received from the arduino
arduino.py
def read_arduino(self):
raw_data = self.ser.readline().decode('utf-8').rstrip()
return raw_data

Read the data all the time, but only emit it when you're running, otherwise throw it away?
def run(self):
while not self.ctrl["break"]:
data = arduino.read_arduino()
if self.ctrl["running"]:
self.data.emit(data)
time.sleep(0.1)

Related

PyQt5 black window while threads are working

I am making window application in PyQt5 and I want to parse data from XML file in the backgrond and send it to second class. I want to use threads with queue to handle between them. When I want to display window app i see black window. It would be nice to use python threads but i tried to do it on QThread and it is not working too idk why...
This is code example
import queue
import sys
import threading
from PyQt5.QtCore import QThread, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import *
class Test_generator:
def __init__(self, queue_obj):
super().__init__()
self.queue_obj = queue_obj
#self.parser_thread = threading.Thread(target=self.parser())
def sender(self):
for _ in range(100):
string ="test"
self.queue_obj.put(string)# send to queue
time.sleep(1)
#print(string)
class Test_collectioner:
def __init__(self,queue_obj):
self.queue_obj = queue_obj
def get_data(self):
collection = []
while self.queue_obj.empty() is False:
print("xd")
print(self.queue_obj.get(), "xd")
#I found this example in the internet(Not working)
class Threaded(QThread):
result = pyqtSignal(int)
def __init__(self, parent=None, **kwargs):
super().__init__(parent, **kwargs)
#pyqtSlot(int)
def run(self):
while True:
print("test")
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.GUI()
self.setWindowTitle("PyQt5 app")
global q
q = queue.Queue()
# BLACKSCREEN (for Test_generator)
test_generator_obj = Test_generator(q)
test_collectioner_obj = Test_collectioner(q)
t1 = threading.Thread(target=test_generator_obj.sender())
t2 = threading.Thread(target=test_collectioner_obj.get_data())
t1.start()
t2.start()
# BLACKSCREEN TOO
"""self.thread = QThread()
self.threaded = Threaded()
self.thread.started.connect(self.threaded.run())
self.threaded.moveToThread(self.thread)
qApp.aboutToQuit.connect(self.thread.quit)
self.thread.start()"""
def GUI(self):
self.showMaximized()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec_())
There are two main problems in your code:
you're executing the methods used for the thread, but you should use the callable object instead; you did the same mistake for the QThread, since connections to signals also expects a callable, but you're actually executing the run function from the main thread (completely blocking everything), since you're using the parentheses; those lines should be like this:
t1 = threading.Thread(target=test_generator_obj.sender)
t2 = threading.Thread(target=test_collectioner_obj.get_data)
or, for the QThread:
self.thread.started.connect(self.threaded.run)
due to the python GIL, multithreading only releases control to the other threads whenever it allows to, but the while cycle you're using prevents that; adding a small sleep function ensures that control is periodically returned to the main thread;
Other problems also exist:
you're already using a subclass of QThread, so there's no use in using another QThread and move your subclass to it;
even assuming that an object is moved to another thread, the slot should not be decorated with arguments, since the started signal of a QThread doesn't have any; also consider that slot decorators are rarely required;
the thread quit() only stops the event loop of the thread, but if run is overridden no event loop is actually started; if you want to stop a thread with a run implementation, a running flag should be used instead; in any other situation, quitting the application is normally enough to stop everything;
Note that if you want to interact with the UI, you can only use QThread and custom signals, and basic python threading doesn't provide a suitable mechanism.
class Threaded(QThread):
result = pyqtSignal(int)
def __init__(self, parent=None, **kwargs):
super().__init__(parent, **kwargs)
def run(self):
self.keepGoing = True
while self.keepGoing:
print("test")
self.msleep(1)
def stop(self):
self.keepGoing = False
class MainWindow(QMainWindow):
def __init__(self):
# ...
self.threaded = Threaded()
self.threaded.start()
qApp.aboutToQuit.connect(self.threaded.stop)

Gracefully shutdown QThread in PySide2

So I'm writing an application using PySide2 that shall redirect everything from stdout to an intermediate queue.Queue. That Queue emits a signal that will be processed by a QThread, appending all strings in the queue to a QTextEdit.
I have looked around SO quite a bit but unfortunately nothing really seems to work for me.
That's the code I'm referring to.
from PySide2 import QtWidgets, QtGui, QtCore
import sys
from queue import Queue
class WriteStream(object):
""" Redirects sys.stdout to a thread-safe Queue
Arguments:
object {object} -- base class
"""
def __init__(self, queue):
self.queue = queue
def write(self, msg):
self.queue.put(msg)
def flush(self):
""" Passing to create non-blocking stream (?!)
https://docs.python.org/3/library/io.html#io.IOBase.flush
"""
pass
class WriteStreamThread(QtCore.QThread):
queue_updated = QtCore.Signal(str)
def __init__(self, queue):
super(WriteStreamThread, self).__init__()
self.stop = False
self.queue = queue
def set_stop(self):
self.stop = True
self.wait() # waits till finished signal has been emitted
def run(self):
while not self.stop: # i guess this is blocking
msg = self.queue.get()
self.queue_updated.emit(msg)
self.sleep(1) # if commented out, app crashes
self.finished.emit()
class Verifyr(QtWidgets.QMainWindow):
def __init__(self, queue, parent=None):
super(Verifyr, self).__init__(parent)
self.centralwidget = QtWidgets.QWidget(self)
layout = QtWidgets.QVBoxLayout(self.centralwidget)
self.textedit = QtWidgets.QTextEdit(self)
layout.addWidget(self.textedit)
self.setLayout(layout)
self.setCentralWidget(self.centralwidget)
print("Verifyr initialised...")
self.listener_thread = WriteStreamThread(queue)
self.listener_thread.queue_updated.connect(self._log_to_qtextedit)
self.listener_thread.start()
#QtCore.Slot(str)
def _log_to_qtextedit(self, msg):
self.textedit.insertPlainText(msg)
if __name__ == '__main__':
# create Queue to be passed to WriteStream and WriteStreamListener
queue = Queue()
# redirect stdout to WriteStream()
sys.stdout = WriteStream(queue)
print("Redirected sys.stdout to WriteStream")
# launching the app
app = QtWidgets.QApplication(sys.argv)
window = Verifyr(queue)
app.aboutToQuit.connect(window.listener_thread.set_stop)
window.show()
sys.exit(app.exec_())
In WriteStreamThread I'm using a while loop that keeps emitting signals, caught by the main application to append to its QTextEdit. When I comment out the self.sleep(1) the application ill be stuck in an infinite loop. If not the thread will exit out just fine.
Can somebody please explain this to me?
UPDATE:
like I mentioned in my comment I've found the bug... here's the updated run() method of WriteStreamThread:
def run(self):
while not self.stop:
try:
msg = self.queue.get(block=False) # nasty little kwarg
except:
msg = "No items in queue. Sleeping 1sec.."
self.sleep(1)
self.queue_updated.emit(msg)
self.finished.emit() # optional
I found the bug. It was queue.get() in the run() method of WriteStreamThread that was blocking.
Changing it to queue.get(block=False) and surrounded with try/catch does the job.
Stupid me...

Is it possible to use a ThreadPoolExecutor with a QThread? Seems to create dummy thread that doesn't terminate

I'm making an application in PyQt5 that runs a variety of long running tasks, such as scraping web pages. In order to avoid crashing the GUI, I've been using QThreads and QObjects
Currently I have a class that inherits QObject and contains a method for scraping the web pages. I move that object on to a QThread, connect a finished signal with a method that quits the thread and then start the thread.
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import threading
from concurrent.futures import ThreadPoolExecutor
import requests
class Main(QMainWindow):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
self.init_ui()
self.test_obj = None
self.thread = QThread(self)
def init_ui(self):
widget = QWidget()
layout = QHBoxLayout()
widget.setLayout(layout)
thread_button = QPushButton("Start Thread")
check_button = QPushButton("Check Thread")
layout.addWidget(thread_button)
layout.addWidget(check_button)
thread_button.clicked.connect(self.work)
check_button.clicked.connect(self.check)
self.setCentralWidget(widget)
self.show()
def work(self):
self.test_obj = TestObject()
self.test_obj.moveToThread(self.thread)
self.test_obj.finished.connect(self.finished)
self.thread.started.connect(self.test_obj.test)
self.thread.start()
def check(self):
for t in threading.enumerate():
print(t.name)
#pyqtSlot()
def finished(self):
self.thread.quit()
self.thread.wait()
print("Finished")
class TestObject(QObject):
finished = pyqtSignal()
def __init__(self):
super(TestObject, self).__init__()
def test(self):
with ThreadPoolExecutor() as executor:
executor.submit(self.get_url)
self.finished.emit()
def get_url(self):
res = requests.get("http://www.google.com/")
print(res)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Main()
sys.exit(app.exec_())
Everything works as expected, I get a:
"Response [200]"
"Finished"
Printed in the console. However when I check the running threads, it shows dummy-1 thread is still running. Every time I run, it creates an additional dummy thread. Eventually I am unable to create any more threads and my application crashes.
Is it possible to use a ThreadPoolExecutor on a QThread like this? If so, is there a correct way to ensure that I don't have these dummy threads still running once I've finished my task?

PyQt4 Wait in thread for user input from GUI

I have a thread class "MyThread" and my main application which is simply called "Gui". I want to create a few objects from the thread class but for this example I created only one object. The thread class does some work, then emits a signal to the Gui class, indicating that a user input is needed (this indication for now is simply changing the text of a button). Then the thread should wait for a user input (in this case a button click) and then continue doing what it is doing...
from PyQt4 import QtGui, QtCore
class MyTrhead(QtCore.QThread):
trigger = QtCore.pyqtSignal(str)
def run(self):
print(self.currentThreadId())
for i in range(0,10):
print("working ")
self.trigger.emit("3 + {} = ?".format(i))
#### WAIT FOR RESULT
time.sleep(1)
class Gui(QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(Gui, self).__init__(parent)
self.setupUi(self)
self.pushButton.clicked.connect(self.btn)
self.t1 = MyTrhead()
self.t1.trigger.connect(self.dispaly_message)
self.t1.start()
print("thread: {}".format(self.t1.isRunning()))
#QtCore.pyqtSlot(str)
def dispaly_message(self, mystr):
self.pushButton.setText(mystr)
def btn(self):
print("Return result to corresponding thread")
if "__main__" == __name__:
import sys
app = QtGui.QApplication(sys.argv)
m = Gui()
m.show()
sys.exit(app.exec_())
How can I wait in (multiple) threads for a user input?
By default, a QThread has an event loop that can process signals and slots. In your current implementation, you have unfortunately removed this behaviour by overriding QThread.run. If you restore it, you can get the behaviour you desire.
So if you can't override QThread.run(), how do you do threading in Qt? An alternative approach to threading is to put your code in a subclass of QObject and move that object to a standard QThread instance. You can then connect signals and slots together between the main thread and the QThread to communicate in both directions. This will allow you to implement your desired behaviour.
In the example below, I've started a worker thread which prints to the terminal, waits 2 seconds, prints again and then waits for user input. When the button is clicked, a second separate function in the worker thread runs, and prints to the terminal in the same pattern as the first time. Please note the order in which I use moveToThread() and connect the signals (as per this).
Code:
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import time
class MyWorker(QObject):
wait_for_input = pyqtSignal()
done = pyqtSignal()
#pyqtSlot()
def firstWork(self):
print 'doing first work'
time.sleep(2)
print 'first work done'
self.wait_for_input.emit()
#pyqtSlot()
def secondWork(self):
print 'doing second work'
time.sleep(2)
print 'second work done'
self.done.emit()
class Window(QWidget):
def __init__(self, parent = None):
super(Window, self).__init__()
self.initUi()
self.setupThread()
def initUi(self):
layout = QVBoxLayout()
self.button = QPushButton('User input')
self.button.setEnabled(False)
layout.addWidget(self.button)
self.setLayout(layout)
self.show()
#pyqtSlot()
def enableButton(self):
self.button.setEnabled(True)
#pyqtSlot()
def done(self):
self.button.setEnabled(False)
def setupThread(self):
self.thread = QThread()
self.worker = MyWorker()
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.firstWork)
self.button.clicked.connect(self.worker.secondWork)
self.worker.wait_for_input.connect(self.enableButton)
self.worker.done.connect(self.done)
# Start thread
self.thread.start()
if __name__ == "__main__":
app = QApplication([])
w = Window()
app.exec_()

How to signal slots in a GUI from a different process?

Context:
In Python a main thread spawns a 2nd process (using multiprocessing module) and then launches a GUI (using PyQt4). At this point the main thread blocks until the GUI is closed. The 2nd process is always processing and ideally should emit signal(s) to specific slot(s) in the GUI in an asynchronous manner.
Question:
Which approach/tools are available in Python and PyQt4 to achieve that and how? Preferably in a soft-interrupt manner rather than polling.
Abstractly speaking, the solution I can think of is a "tool/handler" instantiated in the main thread that grabs the available slots from the GUI instance and connects with the grabbed signals from the 2nd process, assuming I provide this tool some information of what to expect or hard coded. This could be instantiated to a 3rd process/thread.
This is an example Qt application demonstrating sending signals from a child process to slots in the mother process. I'm not sure this is right approach but it works.
I differentiate between process as mother and child, because the word parent is alread used in the Qt context.
The mother process has two threads. Main thread of mother process sends data to child process via multiprocessing.Queue. Child process sends processed data and signature of the signal to be sent to the second thread of mother process via multiprocessing.Pipe. The second thread of mother process actually emits the signal.
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_()
And as convenience also 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_()
Hy all,
I hope this is not considered to much of a necro-dump however I thought it would be good to update Nizam's answer by adding updating his example to PyQt5, adding some comments, removing some python2 syntax and most of all by using the new style of signals available in PyQt. Hope someone finds it useful.
"""
Demo to show how to use PyQt5 and qt signals in combination with threads and
processes.
Description:
Text is entered in the main dialog, this is send over a queue to a process that
performs a "computation" (i.e. capitalization) on the data. Next the process sends
the data over a pipe to the Emitter which will emit a signal that will trigger
the UI to update.
Note:
At first glance it seems more logical to have the process emit the signal that
the UI can be updated. I tried this but ran into the error
"TypeError: can't pickle ChildProc objects" which I am unable to fix.
"""
import sys
from multiprocessing import Process, Queue, Pipe
from PyQt5.QtCore import pyqtSignal, QThread
from PyQt5.QtWidgets import QApplication, QLineEdit, QTextBrowser, QVBoxLayout, QDialog
class Emitter(QThread):
""" Emitter waits for data from the capitalization process and emits a signal for the UI to update its text. """
ui_data_available = pyqtSignal(str) # Signal indicating new UI data is available.
def __init__(self, from_process: Pipe):
super().__init__()
self.data_from_process = from_process
def run(self):
while True:
try:
text = self.data_from_process.recv()
except EOFError:
break
else:
self.ui_data_available.emit(text.decode('utf-8'))
class ChildProc(Process):
""" Process to capitalize a received string and return this over the pipe. """
def __init__(self, to_emitter: Pipe, from_mother: Queue, daemon=True):
super().__init__()
self.daemon = daemon
self.to_emitter = to_emitter
self.data_from_mother = from_mother
def run(self):
""" Wait for a ui_data_available on the queue and send a capitalized version of the received string to the pipe. """
while True:
text = self.data_from_mother.get()
self.to_emitter.send(text.upper())
class Form(QDialog):
def __init__(self, child_process_queue: Queue, emitter: Emitter):
super().__init__()
self.process_queue = child_process_queue
self.emitter = emitter
self.emitter.daemon = True
self.emitter.start()
# ------------------------------------------------------------------------------------------------------------
# Create the UI
# -------------------------------------------------------------------------------------------------------------
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')
# -------------------------------------------------------------------------------------------------------------
# Connect signals
# -------------------------------------------------------------------------------------------------------------
# When enter is pressed on the lineedit call self.to_child
self.lineedit.returnPressed.connect(self.to_child)
# When the emitter has data available for the UI call the updateUI function
self.emitter.ui_data_available.connect(self.updateUI)
def to_child(self):
""" Send the text of the lineedit to the process and clear the lineedit box. """
self.process_queue.put(self.lineedit.text().encode('utf-8'))
self.lineedit.clear()
def updateUI(self, text):
""" Add text to the lineedit box. """
self.browser.append(text)
if __name__ == '__main__':
# Some setup for qt
app = QApplication(sys.argv)
# Create the communication lines.
mother_pipe, child_pipe = Pipe()
queue = Queue()
# Instantiate (i.e. create instances of) our classes.
emitter = Emitter(mother_pipe)
child_process = ChildProc(child_pipe, queue)
form = Form(queue, emitter)
# Start our process.
child_process.start()
# Show the qt GUI and wait for it to exit.
form.show()
app.exec_()
One should first look how Signals/Slots work within only one Python process:
If there is only one running QThread, they just call the slots directly.
If the signal is emitted on a different thread it has to find the target thread of the signal and put a message/ post an event in the thread queue of this thread. This thread will then, in due time, process the message/event and call the signal.
So, there is always some kind of polling involved internally and the important thing is that the polling is non-blocking.
Processes created by multiprocessing can communicate via Pipes which gives you two connections for each side.
The poll function of Connection is non-blocking, therefore I would regularly poll it with a QTimer and then emit signals accordingly.
Another solution might be to have a Thread from the threading module (or a QThread) specifically just waiting for new messages from a Queue with the get function of the queue. See the Pipes and Queues part of multiprocessing for more information..
Here is an example starting a Qt GUI in another Process together with a Thread who listens on a Connection and upon a certain message, closes the GUI which then terminates the process.
from multiprocessing import Process, Pipe
from threading import Thread
import time
from PySide import QtGui
class MyProcess(Process):
def __init__(self, child_conn):
super().__init__()
self.child_conn = child_conn
def run(self):
# start a qt application
app = QtGui.QApplication([])
window = QtGui.QWidget()
layout = QtGui.QVBoxLayout(window)
button = QtGui.QPushButton('Test')
button.clicked.connect(self.print_something)
layout.addWidget(button)
window.show()
# start thread which listens on the child_connection
t = Thread(target=self.listen, args = (app,))
t.start()
app.exec_() # this will block this process until somebody calls app.quit
def listen(self, app):
while True:
message = self.child_conn.recv()
if message == 'stop now':
app.quit()
return
def print_something(self):
print("button pressed")
if __name__ == '__main__':
parent_conn, child_conn = Pipe()
s = MyProcess(child_conn)
s.start()
time.sleep(5)
parent_conn.send('stop now')
s.join()
A quite interesting topic. I guess having a signal that works between threads is a very useful thing. How about creating a custom signal based on sockets?
I haven't tested this yet, but this is what I gathered up with some quick investigation:
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')
Might just put you on the right track.
I had the same problem in C++. From a QApplication, I spawn a Service object. The object creates the Gui Widget but it's not its parent (the parent is QApplication then). To control the GuiWidget from the service widget, I just use signals and slots as usual and it works as expected.
Note: The thread of GuiWidget and the one of the service are different. The service is a subclass of QObject.
If you need multi process signal/slot mechanism, then try to use Apache Thrift or use a Qt-monitoring process which spawns 2 QProcess objects.

Categories