In code below a signal is processed (triggering slot) without calling app.exec().
import sys
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot
class Worker(QObject):
signal = pyqtSignal(int)
def run(self):
self.signal.emit(1)
class MyWindow(QMainWindow):
def __init__(self):
super().__init__()
worker = Worker()
worker.signal.connect(self.slot)
worker.run()
#pyqtSlot(int)
def slot(self, num):
print(num)
app = QApplication(sys.argv)
window = MyWindow()
window.show()
# app.exec()
As I know, in PyQt a signal is sent to an event queue when emit() is called, and processed when exec() of an event loop is called.
But the signal above seems to be processed without any event loop.
So I want the signal above to be processed only after app.exec() is called. Is it possible?
Related
I've tried several methods to stop a Qthread, but none seem to work. When I look at the Call Stack, the thread has not ended, and while click again, another thread starts, thus getting infinite active threads and consuming more unnecessary memory. I tried to put terminate() and quit() and nothing stops the thread. So I also tried all of them together and no result
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5.QtCore import QThread
from PyQt5 import QtWidgets
from PyQt5 import QtCore
import time
import sys
class MyThread(QThread):
def run(self):
x=0
print("Starting...")
while x<5:
time.sleep(1.5)
print("Running...")
x=x+1
def close(self):
self.isRunning=False
self.terminate()
self.wait()
self.quit()
self.wait()
print("Done")
class Ui_MainWindow(QMainWindow):
def __init__(self):
super(Ui_MainWindow, self).__init__()
self.btn = QtWidgets.QPushButton(self)
self.btn.setGeometry(QtCore.QRect(77, 30, 50, 30))
self.btn.setText("CLICK")
self.btn.clicked.connect(self.doSomething)
def doSomething(self,event):
self.worker=MyThread()
self.worker.setTerminationEnabled(True)
self.worker.finished.connect(self.worker.close)
self.worker.start()
if __name__ == "__main__":
app = QApplication([])
ui = Ui_MainWindow()
ui.show()
sys.exit(app.exec_())
I managed to solve my problem using threading native library and with pyqtSignal. When clicking on the button, it loses its connection and starts the thread. After the end of the loop in the thread, a signal is issued and thus returning the connection to the button. This way, it prevents the user from clicking the button several times.
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5.QtCore import pyqtSignal
from PyQt5 import QtWidgets
from PyQt5 import QtCore
import time
import sys
import threading
class Ui_MainWindow(QMainWindow):
signal=pyqtSignal(str)
def __init__(self):
super(Ui_MainWindow, self).__init__()
self.btn = QtWidgets.QPushButton(self)
self.btn.setGeometry(QtCore.QRect(77, 30, 50, 30))
self.btn.setText("CLICK")
self.signal.connect(self.signalResponse)
self.btn.clicked.connect(self.doSomething)
def doSomething(self,event):
print("Starting...")
self.btn.disconnect()
worker=threading.Thread(target=self.worker)
worker.start()
def signalResponse(self,response):
if response=="Finished":
self.btn.clicked.connect(self.doSomething)
print(response)
def worker(self):
x=0
while x<5:
time.sleep(0.5)
print("Running...")
x=x+1
self.signal.emit("Finished")
if __name__ == "__main__":
app = QApplication([])
ui = Ui_MainWindow()
ui.show()
sys.exit(app.exec_())
I use a pyqt_signal to transmit a sub window, which has a button whose function is to print. I use a thread to transmit this sub window to the main window to show, however the button loses its function. I know that I should put the statement self.sub_window = SubWindow() into the __init__ function in the second class, but how can I achieve the same effect if I still put this statement here.
# -*- coding: utf-8 -*-
from threading import currentThread
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton
import sys
from PyQt5.QtCore import pyqtSignal, QObject, QThread
class SubWindow(QWidget):
def __init__(self):
super(SubWindow, self).__init__()
self.resize(400, 400)
self.button = QPushButton(self)
self.button.setText('push me to print ***')
self.button.move(200, 200)
self.button.clicked.connect(self.print_)
def print_(self):
print('***')
class SignalStore(QThread):
window_signal = pyqtSignal(object)
def __init__(self):
super(SignalStore, self).__init__()
def run(self):
# if i put this statement here, how can i acquire window's print button function
self.sub_window = SubWindow()
self.window_signal.emit(self.sub_window)
class MainWindow(QWidget):
def __init__(self):
super(MainWindow, self).__init__()
self.resize(400, 400)
self.button = QPushButton(self)
self.button.setText('push me to get subwindow')
self.button.move(200, 200)
self.button.clicked.connect(self.send_signal)
self.med_signal = SignalStore()
self.med_signal.window_signal.connect(self.get_sub_window)
def send_signal(self):
self.med_signal.start()
def get_sub_window(self, para):
self.sub_window = para
self.sub_window.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
win = MainWindow()
win.show()
sys.exit(app.exec_())
Don't create or access gui objects inside threads. Read Qt guide.
GUI Thread and Worker Thread
As mentioned, each program has one thread when it is started. This thread is called the "main thread" (also known as the "GUI thread" in Qt applications). The Qt GUI must run in this thread. All widgets and several related classes, for example QPixmap, don't work in secondary threads. A secondary thread is commonly referred to as a "worker thread" because it is used to offload processing work from the main thread.
This is probably what you are looking for:
import time
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton
import sys
from PyQt5.QtCore import pyqtSignal, QThread, pyqtSlot
class SubWindow(QWidget):
def __init__(self, parent=None):
super(SubWindow, self).__init__(parent)
self.resize(400, 400)
self.button = QPushButton(self)
self.button.setText('push me to print ***')
self.button.move(200, 200)
self.button.clicked.connect(self.print_)
#pyqtSlot()
def print_(self):
print('hello from subwindow')
class SignalStore(QThread):
print_func = pyqtSignal(str)
def __init__(self):
super(SignalStore, self).__init__()
def run(self):
time.sleep(1) # fake working...
self.print_func.emit("hello from thread")
class MainWindow(QWidget):
def __init__(self):
super(MainWindow, self).__init__()
self.resize(400, 400)
self.subwin = SubWindow()
self.button = QPushButton(self)
self.button.setText('push me to get subwindow')
self.button.move(200, 200)
self.button.clicked.connect(self.send_signal)
self.med_signal = SignalStore()
self.med_signal.print_func.connect(self.print_from_main)
def send_signal(self):
self.subwin.show()
self.med_signal.start()
#pyqtSlot(str)
def print_from_main(self, string: str):
print(string)
self.subwin.print_()
if __name__ == '__main__':
app = QApplication(sys.argv)
win = MainWindow()
win.show()
sys.exit(app.exec_())
I am trying to process every QVideoFrame from QMediaPlayer and display it on QLabel.
Here is the sample code.
QMediaPlayer sends QVideoFrame to QVideoSink
Video sink sends the frame to MyWorker.
MyWorker converts QVideoFrame to QPixmap, does some processing and sends the pixmap to QLabel.
from PySide6.QtCore import QObject, QUrl, Signal, Slot
from PySide6.QtGui import QPixmap
from PySide6.QtWidgets import QLabel, QApplication
from PySide6.QtMultimedia import QMediaPlayer, QVideoSink, QVideoFrame
import sys
import time
class MyWorker(QObject):
pixmapChanged = Signal(QPixmap)
#Slot(QVideoFrame)
def setVideoFrame(self, frame: QVideoFrame):
qimg = frame.toImage()
pixmap = QPixmap.fromImage(qimg)
time.sleep(0.5) # represents time-consuming work
self.pixmapChanged.emit(pixmap)
app = QApplication(sys.argv)
player = QMediaPlayer()
sink = QVideoSink()
worker = MyWorker()
widget = QLabel()
player.setVideoSink(sink)
sink.videoFrameChanged.connect(worker.setVideoFrame)
worker.pixmapChanged.connect(widget.setPixmap)
player.setSource(QUrl.fromLocalFile("my-video.mp4"))
widget.show()
player.play()
app.exec()
app.quit()
Processing takes some time, so the label is updating slower than the original video FPS. This is fine, but the problem is that my current code without QThread is blocking the GUI.
Using player.moveToThread(...) didn't work. How can I run QMediaPlayer in separate thread?
Thanks to #musicamente, I fixed it by moving the worker to separate thread.
Note that the window sends the frame to worker only when the worker is ready. If not, blocked signals are queued with QVideoFrame instance and quickly consume all the memory.
Media player
from PySide6.QtCore import QObject, Signal, Slot, QThread, QUrl
from PySide6.QtGui import QPixmap
from PySide6.QtMultimedia import QMediaPlayer, QVideoSink, QVideoFrame
from PySide6.QtWidgets import QMainWindow, QLabel
import time
class FrameWorker(QObject):
pixmapChanged = Signal(QPixmap)
def __init__(self, parent=None):
super().__init__(parent)
self.ready = True
#Slot(QVideoFrame)
def setVideoFrame(self, frame: QVideoFrame):
self.ready = False
time.sleep(1) # represents time-consuming work
self.pixmapChanged.emit(QPixmap.fromImage(frame.toImage()))
self.ready = True
class FrameSender(QObject):
frameChanged = Signal(QVideoFrame)
class Window(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.workerThread = QThread()
self.player = QMediaPlayer()
self.frameSender = FrameSender()
self.frameWorker = FrameWorker()
self.displayLabel = QLabel()
self.frameWorker.moveToThread(self.workerThread)
self.workerThread.start()
self.player.setVideoSink(QVideoSink(self))
self.player.videoSink().videoFrameChanged.connect(self.onFramePassedFromPlayer)
self.frameSender.frameChanged.connect(self.frameWorker.setVideoFrame)
self.frameWorker.pixmapChanged.connect(self.displayLabel.setPixmap)
self.setCentralWidget(self.displayLabel)
#Slot(QVideoFrame)
def onFramePassedFromPlayer(self, frame: QVideoFrame):
if self.frameWorker.ready:
self.frameSender.frameChanged.emit(frame)
def closeEvent(self, event):
self.workerThread.quit()
self.workerThread.wait()
super().closeEvent(event)
if __name__ == "__main__":
from PySide6.QtWidgets import QApplication
import sys
app = QApplication(sys.argv)
window = Window()
window.player.setSource(QUrl.fromLocalFile("my-video.mp4"))
window.show()
window.player.play()
app.exec()
app.quit()
Camera
from PySide6.QtCore import QObject, Signal, Slot, QThread
from PySide6.QtGui import QPixmap
from PySide6.QtMultimedia import QMediaCaptureSession, QVideoSink, QVideoFrame, QCamera
from PySide6.QtWidgets import QMainWindow, QLabel
import time
class FrameWorker(QObject):
pixmapChanged = Signal(QPixmap)
def __init__(self, parent=None):
super().__init__(parent)
self.ready = True
#Slot(QVideoFrame)
def setVideoFrame(self, frame: QVideoFrame):
self.ready = False
time.sleep(1) # represents time-consuming work
self.pixmapChanged.emit(QPixmap.fromImage(frame.toImage()))
self.ready = True
class FrameSender(QObject):
frameChanged = Signal(QVideoFrame)
class Window(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.workerThread = QThread()
self.captureSession = QMediaCaptureSession()
self.frameSender = FrameSender()
self.frameWorker = FrameWorker()
self.displayLabel = QLabel()
self.frameWorker.moveToThread(self.workerThread)
self.workerThread.start()
self.captureSession.setVideoSink(QVideoSink(self))
self.captureSession.videoSink().videoFrameChanged.connect(
self.onFramePassedFromCamera
)
self.frameSender.frameChanged.connect(self.frameWorker.setVideoFrame)
self.frameWorker.pixmapChanged.connect(self.displayLabel.setPixmap)
self.setCentralWidget(self.displayLabel)
camera = QCamera(self)
self.captureSession.setCamera(camera)
camera.start()
#Slot(QVideoFrame)
def onFramePassedFromCamera(self, frame: QVideoFrame):
if self.frameWorker.ready:
self.frameSender.frameChanged.emit(frame)
def closeEvent(self, event):
self.workerThread.quit()
self.workerThread.wait()
super().closeEvent(event)
if __name__ == "__main__":
from PySide6.QtWidgets import QApplication
import sys
app = QApplication(sys.argv)
window = Window()
window.show()
app.exec()
app.quit()
When calling a thread class in PyQt5, the application crashes with error: QThread: Destroyed while thread is still running if I don't add
def __del__(self):
self.wait()
After adding the above statement to the thread class, the application runs but stalls until the thread has finished executing (basically making the thread useless). I'm running macOS Catalina 10.15.6, Intel i9, Python 3.7.4. Any way to resolve this? Here is the code (the ui window only has one button in it):
from PyQt5.QtWidgets import QMainWindow
from PyQt5 import QtWidgets, uic
from PyQt5.QtCore import QThread, pyqtSignal
import sys, time
class Main(QMainWindow):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
uic.loadUi('main.ui', self)
self.btn1.clicked.connect(self.run_worker)
def run_worker(self):
worker = Worker_thread()
worker.start()
class Worker_thread(QThread):
def __init__(self):
QThread.__init__(self)
def __del__(self):
self.wait()
def run(self):
time.sleep(10)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
win = Main()
win.show()
sys.exit(app.exec_())
The problem is simple: The "worker" variable is a local variable that when its scope ends it is eliminated, in this case the scope is the "run_worker" function. When the "run" method is eliminated, it is not executed in the secondary thread that manages the QThread but in the main thread blocking the GUI.
The solution is to extend its scope, for example by making class attribute:
def run_worker(self):
self.worker = Worker_thread()
self.worker.start()
Note: The problem is not OS dependent.
In the following code I try to deal with QThread. In this executable example there are three buttons: first for start, second for stop and third for close. Well, when I start the task its runs like a charm. BUT when I want the while-loop to stop I click on the stop-button. And now, there is a problem: the while-loop doesn't stop.
You see, the stop-button emits a signal to call the stop() method on TestTask().
What is wrong?
from sys import argv
from PyQt4.QtCore import QObject, pyqtSignal, QThread, Qt, QMutex
from PyQt4.QtGui import QDialog, QApplication, QPushButton, \
QLineEdit, QFormLayout, QTextEdit
class TestTask(QObject):
def __init__(self, parent=None):
QObject.__init__(self, parent)
self._mutex = QMutex()
self._end_loop = True
def init_object(self):
while self._end_loop:
print "Sratus", self._end_loop
def stop(self):
self._mutex.lock()
self._end_loop = False
self._mutex.unlock()
class Form(QDialog):
stop_loop = pyqtSignal()
def __init__(self, parent=None):
QDialog.__init__(self, parent)
self.init_ui()
def init_ui(self):
self.pushButton_start_loop = QPushButton()
self.pushButton_start_loop.setText("Start Loop")
self.pushButton_stop_loop = QPushButton()
self.pushButton_stop_loop.setText("Stop Loop")
self.pushButton_close = QPushButton()
self.pushButton_close.setText("Close")
layout = QFormLayout()
layout.addWidget(self.pushButton_start_loop)
layout.addWidget(self.pushButton_stop_loop)
layout.addWidget(self.pushButton_close)
self.setLayout(layout)
self.setWindowTitle("Tes Window")
self.init_signal_slot_pushButton()
def start_task(self):
self.task_thread = QThread(self)
self.task_thread.work = TestTask()
self.task_thread.work.moveToThread(self.task_thread)
self.task_thread.started.connect(self.task_thread.work.init_object)
self.stop_loop.connect(self.task_thread.work.stop)
self.task_thread.start()
def stop_looping(self):
self.stop_loop.emit()
def init_signal_slot_pushButton(self):
self.pushButton_start_loop.clicked.connect(self.start_task)
self.pushButton_stop_loop.clicked.connect(self.stop_looping)
self.pushButton_close.clicked.connect(self.close)
app = QApplication(argv)
form = Form()
form.show()
app.exec_()
The stop_loop signal is converted to an event and sent to the thread of the signal receiver. But your worker object is running a blocking while-loop, and this prevents the thread processing any pending events in its event-queue. So the slot connected to the stop_loop signal will never be called.
To work around this, you can call processEvents in the while-loop to allow the thread to process its pending events:
def init_object(self):
while self._end_loop:
QThread.sleep(1)
QApplication.processEvents()
print "Status", self._end_loop
Alternatively, you could call the worker's stop() method directly instead of emitting a signal. Strictly speaking, this is not thread-safe, but it would only be a problem if multiple threads could call stop() at the same time. So a more correct way to do that is to use a mutex to protect the value being changed:
class TestTask(QObject):
def __init__(self, parent=None):
QObject.__init__(self, parent)
self._mutex = QtCore.QMutex()
self._end_loop = True
def stop(self):
self._mutex.lock()
self._end_loop = False
self._mutex.unlock()
And now calling stop() directly from the main thread is thread-safe.