I wanna resize my QWidget from another thread made with QThread but when i do this my application stuck in loading (crashed)
this is my code:
from PyQt5.QtCore import QThread
from PyQt5.QtWidgets import QApplication, QWidget
from sys import exit
Application = QApplication([])
Window = QWidget()
thread = QThread()
thread.run = lambda: Window.resize(400, 100)
Window.show()
thread.start()
exit(Application.exec_())
No access to QWidgets is allowed from threads other than the main Qt thread.
Note that access means: creating new widgets, reading/writing widget properties or calls to any of the widget functions.
The only and correct way to do so is to use signals. This allows Qt to queue the signal (in case it detects that the sender or the receiver are on different threads) and rely to the receiver as soon as its event queue has been correctly processed.
In order to do so, it's usually better to subclass QThread (it could be theoretically possible to do so without subclassing, but there's no actual benefit and it might just make things more complicated than required).
from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.QtWidgets import QApplication, QWidget
from sys import exit
from time import sleep
class Mover(QThread):
move = pyqtSignal(int, int)
def run(self):
sleep(5)
self.move.emit(400, 100)
Application = QApplication([])
Window = QWidget()
thread = Mover()
thread.move.connect(Window.move)
Window.show()
thread.start()
exit(Application.exec_())
Related
I am using Python Qt (PySide2) which has a GUI in the main thread and a library that does IO, data-crunching, etc in a second thread.
I use signals & slots to update the GUI. In the examples I have seen on SO, the signal is always created in the worker (non-GUI) thread. Is it necessary to do this?
Reason: my library can be used with a GUI or could be used in another Python script. So, it might output data to the GUI or maybe to console/log file. To make the code in the library generic, I thought that whatever calls the library can register a callback. That callback can be to emit to Qt or output to file, etc.
Here's an example where the Signal is created in the GUI thread. It works, but could it cause thread-related issues?
import threading
import time
import sys
from PySide2.QtWidgets import QWidget, QVBoxLayout, QTextEdit, QPushButton, QApplication
from PySide2 import QtCore
class aio_connection(QtCore.QObject):
data_recvd = QtCore.Signal(object)
class TextEditDemo(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("TEST")
self.resize(600,540)
self.textEdit = QTextEdit()
self.btnPress1 = QPushButton("Run")
layout = QVBoxLayout()
layout.addWidget(self.textEdit)
layout.addWidget(self.btnPress1)
self.setLayout(layout)
self.btnPress1.clicked.connect(self.btnPress1_Clicked)
self.aio_conn = aio_connection() # Signal is created in main (GUI) thread
# Connect signals (data_recvd) and slots (on_data_ready)
self.aio_conn.data_recvd.connect(self.on_data_ready)
def btnPress1_Clicked(self):
threading.Thread(target=bg_task, args=(self.cb,)).start()
#QtCore.Slot()
def on_data_ready(self, msg):
self.textEdit.append(msg)
def cb(self, info):
self.aio_conn.data_recvd.emit(info)
# Simulate the library that runs in a second thread
def bg_task(callback):
for i in range(100):
callback(str(i))
time.sleep(0.1)
def main():
app = QApplication(sys.argv)
win = TextEditDemo()
win.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
By default, signal connections are made using the AutoConnection type. This means the eventual type of connection will be automatically made at runtime, when the signal is actually emitted. If the sender and receiver are in different threads, Qt will use a queued connection, which will post an event to the event-queue of the receiving thread. Thus, when control returns to the receiver, the event will be processed and any connected slots will be called at that time.
This is an important consideration when updating GUI elements from worker threads, because Qt does not support GUI-related operations of any kind outside the main thread. However, so long as you always use signals to communicate between threads using the default connection-type, you should not have any problems - Qt will automatically guarantee they are done in a thread-safe way.
Below is a version of your scripts that verifies everything is working as desired. When I run it, I get the following output, which shows the current thread-id within each function call:
main: 4973
bg_task: 4976
cb: 4976
on_data_ready: 4973
cb: 4976
on_data_ready: 4973
cb: 4976
...
import threading, time, sys
from PySide2.QtWidgets import QWidget, QVBoxLayout, QTextEdit, QPushButton, QApplication
from PySide2 import QtCore
class aio_connection(QtCore.QObject):
data_recvd = QtCore.Signal(object)
class TextEditDemo(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("TEST")
self.resize(600,540)
self.textEdit = QTextEdit()
self.btnPress1 = QPushButton("Run")
layout = QVBoxLayout()
layout.addWidget(self.textEdit)
layout.addWidget(self.btnPress1)
self.setLayout(layout)
self.btnPress1.clicked.connect(self.btnPress1_Clicked)
self.aio_conn = aio_connection()
self.aio_conn.data_recvd.connect(self.on_data_ready)
def btnPress1_Clicked(self):
threading.Thread(target=bg_task, args=(self.cb,)).start()
#QtCore.Slot()
def on_data_ready(self, msg):
print('on_data_ready:', threading.get_native_id())
self.textEdit.append(msg)
def cb(self, info):
print('cb:', threading.get_native_id())
self.aio_conn.data_recvd.emit(info)
def bg_task(callback):
print('bg_task:', threading.get_native_id())
for i in range(5):
callback(str(i))
time.sleep(0.1)
def main():
print('main:', threading.get_native_id())
app = QApplication(sys.argv)
win = TextEditDemo()
win.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
I try to modify the original code written in PyQt5 to PySide2 as the license problems. In the original code (PyQt5), I use multiprocessing in QThread to increase CPU usage, and it works perfectly. However, after modified to PySide2, the code generates a series of error and destroy the GUI. The following is a minimized example to cause the problem.
The following code works well when I use PyQt5 instead of PySide2.
import sys
import multiprocessing as mp
from PyQt5.QtCore import QThread
from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton
class Task(QThread):
def run(self):
print('task started')
with mp.Pool() as pool:
res = pool.map(mp_task, range(10000))
print('task finished', res)
def mp_task(x):
# some heavy tasks
ret = 0
for i in range(x + 50000):
ret += i
return ret
class Gui(QMainWindow):
def __init__(self):
super().__init__()
button = QPushButton('click me')
button.clicked.connect(self.do_task)
self.setCentralWidget(button)
def do_task(self):
self.thread = Task()
self.thread.start()
def main():
app = QApplication(sys.argv)
window = Gui()
window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
However, if I change the import package from PyQt5 to PySide2, for example,
from PySide2.QtCore import QThread
from PySide2.QtWidgets import QApplication, QMainWindow, QPushButton
after clicking the button, the GUI will be destroyed and the program generates the following error messages (some repeated messages has been omitted):
QObject: Cannot create children for a parent that is in a different thread.
(Parent is QApplication(0x1cb19c0), parent's thread is QThread(0x1948cc0), current thread is Task(0x7fc1d0005390)
QObject: Cannot create children for a parent that is in a different thread.
(Parent is QApplication(0x1cb19c0), parent's thread is QThread(0x1948cc0), current thread is Task(0x7fc1d0005390)
QObject: Cannot create children for a parent that is in a different thread.
(Parent is QApplication(0x1cb19c0), parent's thread is QThread(0x1948cc0), current thread is Task(0x7fc1d0005390)
qt.qpa.xcb: QXcbConnection: XCB error: 128 (Unknown), sequence: 548, resource id: 88080397, major code: 130 (Unknown), minor code: 2
qt.qpa.xcb: QXcbConnection: XCB error: 128 (Unknown), sequence: 552, resource id: 88080397, major code: 130 (Unknown), minor code: 2
QObject: Cannot create children for a parent that is in a different thread.
(Parent is QApplication(0x1cb19c0), parent's thread is QThread(0x1948cc0), current thread is Task(0x7fc1d0005390)
Maybe there are some differences between PyQt5 and PySide2 in QThread and lead to this fatal error. If I do not want to move QApplication and Gui into global scope, how could I fix the problem?
Testing environment: Ubuntu 18.04 64-bit, Python3.6, PyQt5 5.11.2, PySide2 5.11.1
Interestingly, if I put the contents of the main() function to global scope, the problem solved! For example:
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Gui()
window.show()
sys.exit(app.exec_())
Furthermore, Qt6 will soon be released with multiple Python integration improvements. This problem might be solved with the new version of PySide.
This question already has an answer here:
PyQt4 - creating a timer
(1 answer)
Closed 4 years ago.
I created a PyQt5 GUI using QtDesigner and converted it to Python. I was planning to update and display the value from the sensor reading from the Raspberry Pi. Since the GUI is in a loop, there is no way I can update data from outside that loop. Currently I used the code below where I use the QTimer widget that executes the function every given interval. Is this solution appropriate or not? What other methods are available to accomplish this task?
from PyQt5 import QtCore, QtGui, QtWidgets
from uimainwindow import Ui_MainWindow
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
numTest=0;
def __init__(self):
QtWidgets.QMainWindow.__init__(self)
self.setupUi(self)
QtCore.QTimer.singleShot(1000, self.getSensorValue)
def getSensorValue(self):
print(self.numTest)
self.numTest=self.numTest+1
QtCore.QTimer.singleShot(1000, self.getSensorValue)
if __name__=="__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w=MainWindow()
w.show()
sys.exit(app.exec_())
To use a QTimer which calls a member function periodically:
Make a member variable of QTimer.
Set interval of QTimer to the intended delay.
Connect getSensorValue() as signal handler to QTimer.timeout().
Start QTimer member.
Demo test-QTimer.py:
#!/usr/bin/python3
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel
from PyQt5.QtCore import QTimer
class MainWindow(QMainWindow):
# constructor
def __init__(self):
QMainWindow.__init__(self)
# counter
self.i = 0
# add QLabel
self.qLbl = QLabel('Not yet initialized')
self.setCentralWidget(self.qLbl)
# make QTimer
self.qTimer = QTimer()
# set interval to 1 s
self.qTimer.setInterval(1000) # 1000 ms = 1 s
# connect timeout signal to signal handler
self.qTimer.timeout.connect(self.getSensorValue)
# start timer
self.qTimer.start()
def getSensorValue(self):
self.i += 1
# print('%d. call of getSensorValue()' % self.i)
self.qLbl.setText('%d. call of getSensorValue()' % self.i)
qApp = QApplication(sys.argv)
# setup GUI
qWin = MainWindow()
qWin.show()
# run application
sys.exit(qApp.exec_())
Tested in cygwin on Windows 10:
So I have created a GUI using QT Designer. It works pretty well, but on more complex calls it doesn't update the main window and locks up. I want to run my CustomComplexFunction() while updating a textEdit in the main window from constantly changing backend information, and I wanted it to run every 2 seconds. The following code seems right and runs without errors, but doesn't update the textEdit. Please note i'm importing a .ui file designed from QT Designer with a pushButton and textEdit and the code won't run without it.
Main.py
import sys
from PyQt5.QtWidgets import QDialog, QApplication, QPushButton, QVBoxLayout, QMainWindow
from PyQt5.QtCore import QCoreApplication, QObject, QRunnable, QThread, QThreadPool, pyqtSignal, pyqtSlot
from PyQt5 import uic, QtGui
class Worker(QObject):
newParams = pyqtSignal()
#pyqtSlot()
def paramUp(self):
x=1
for x in range(100):
time.sleep(2)
self.newParams.emit()
print ("sent new params")
x +=1
Ui_somewindow, _ = uic.loadUiType("mainwindow.ui") #the path to UI
class SomeWindow(QMainWindow, Ui_somewindow, QDialog):
def __init__(self):
QMainWindow.__init__(self)
Ui_somewindow.__init__(self)
self.setupUi(self)
# Start custom functions
self.params = {}
self.pushButton.clicked.connect(self.complex) #NumEvent
def complex(self):
self.work = Worker()
self.thread = QThread()
self.work.newParams.connect(self.changeTextEdit)
self.work.moveToThread(self.thread)
self.thread.start()
self.CustomComplexFunction()
def CustomComplexFunction(self):
self.params['City'] = 'Test'
def changeTextEdit(self):
try:
City = self.params['City']
self.textEdit_14.setPlainText(City)
except KeyError:
City = None
if __name__ == "__main__":
app = QApplication(sys.argv)
window = SomeWindow()
window.show()
sys.exit(app.exec_())
You can see the official docs for Signals and Slots here and this SO post was also very helpful, but it seems like I built it correctly. According to the docs, the emitter doesn't care if the signal is used. This might be why the code doesn't have errors but doesn't work either.
Any ideas on how to make it work? Or atleast some way to test the emitter and signals??
You have forgot to connect the thread to the worker object.
self.work = Worker()
self.thread = QThread()
self.thread.started.connect(self.worker.work) # <--new line, make sure work starts.
self.thread.start()
Good luck with the application :-)
How to call a method asynchronously in PyQt5 using Python3?
I have tried to use a signal to do it.
import sys
from time import sleep
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel
class MainWindow(QMainWindow):
asyncFuncSignal = pyqtSignal()
def __init__(self):
super().__init__()
self.initUi()
def initUi(self):
self.label = QLabel(self)
self.label.setText("loading...")
# Trying to call 'self.asyncFunc' asynchronously
self.asyncFuncSignal.connect(self.asyncFunc)
self.asyncFuncSignal.emit()
print("loaded")
def asyncFunc(self):
# Doing something hard that takes time
# I have used 'sleep' to implement the delay
sleep(2)
self.label.setText("done")
print("asyncFunc finished")
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
This program tries to finish asyncFunc before writing "loaded". But I would like the program to finish initUi immediately with shown loading... in the label and after that the text done appears in 2 seconds.
What is the best and the shortest way to do it?
It's said here http://pyqt.sourceforge.net/Docs/PyQt5/signals_slots.html I can use queued connections but I haven't found an example how to implement it.
PyQt5.QtCore.Qt.QueuedConnection may help. Just replace
self.asyncFuncSignal.connect(self.asyncFunc)
with
from PyQt5.QtCore import Qt
...
self.asyncFuncSignal.connect(self.asyncFunc, Qt.QueuedConnection)