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)
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 am using a QThread and, when my worker class is dont with the big work, i want to call a function that does changes to my UI (in this case with .finished.connect(), but i dont fully understand how the .connect() step works and how i can pass variables to the function called.
As an example: A button that, when pressed, opens a Thread in which it counts up, and changes the button text to the number its on. When the counter is finished, it prints something. Very simple and it works just fine.
main.py:
from PyQt5 import QtWidgets
import sys
import worker
from PyQt5.QtCore import QThread
from start import test
class App(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(App, self).__init__(parent)
self.init_ui()
self.initiate_buttons()
self.obj = worker.Worker()
self.thread = QThread()
self.obj.intReady.connect(self.onIntReady)
self.obj.moveToThread(self.thread)
self.obj.finished.connect(self.thread.quit)
self.obj.finished.connect(test)
self.thread.started.connect(self.obj.procCounter)
def init_ui(self):
self.pushButton = QtWidgets.QPushButton(self)
def onIntReady(self, i):
self.pushButton.setText("{}".format(i))
def initiate_buttons(self):
print("clicked")
self.pushButton.clicked.connect(lambda: self.thread.start())
def run():
application = QtWidgets.QApplication(sys.argv)
main_app = App()
main_app.show()
application.exec()
if __name__ == '__main__':
run()
worker.py:
from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot
import time
class Worker(QObject):
finished = pyqtSignal()
intReady = pyqtSignal(int)
#pyqtSlot()
def procCounter(self):
for i in range(1, 5):
time.sleep(1)
self.intReady.emit(i)
self.finished.emit()
start.py:
def test(x="bar"):
print("test"+x)
So the thing i am talking about is start.py - i can put the function in its own module just fine and it worked. But what if i want to pass a parameter to it? I tried the obvious attempt, changing self.obj.finished.connect(test) to self.obj.finished.connect(test(x="foo")) - but that doesnt work. But i dont understand why it does not work, and how else i am supposed to do this. In the end, i want the module to do changes to my UI, just like onIntReady. I found this and this but did not understand how i can apply it.
Using PyQt5, I want to implement a two windows displaying one after another automatically, without the user interacting with any window. Something like this:
While True:
Show Window1
wait 2 seconds
Close Window1
Show Window2
wait 2 seconds
Close Window2
The problem I am having is that the main UI thread is stuck in app.exec_() function, so it cannot implement the opening and closing logic.
import sys
from PyQt5.QtWidgets import *
from PyQt5 import uic
class Win1(QMainWindow):
def __init__(self):
super(Win1, self).__init__()
uic.loadUi('win1.ui', self)
self.show()
class Win2(QMainWindow):
def __init__(self):
super(Win2, self).__init__()
uic.loadUi('win2.ui', self)
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
while True:
win = Win1()
time.sleep(1)
win.close()
win = Win2()
time.sleep(1)
win.close()
app.exec_() # <--------- Program blocks here
I would appreciate if someone can share a minimal example for this working without blocking. Or please point to the mechanism that should be used.
If you are going to work with Qt then you should forget about sequential logic but you have to implement the logic using events. For example, in your case you want one window to be shown every time T and another to be hidden, so that can be implemented with a QTimer and a flag:
import sys
from PyQt5.QtCore import QTimer
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5 import uic
class Win1(QMainWindow):
def __init__(self):
super(Win1, self).__init__()
uic.loadUi('win1.ui', self)
self.show()
class Win2(QMainWindow):
def __init__(self):
super(Win2, self).__init__()
uic.loadUi('win2.ui', self)
self.show()
if __name__ == "__main__":
app = QApplication(sys.argv)
timer = QTimer()
timer.setProperty("flag", True)
win1 = Win1()
win2 = Win2()
def on_timeout():
flag = timer.property("flag")
if flag:
win1.show()
win2.close()
else:
win2.show()
win1.close()
timer.setProperty("flag", not flag)
timer.timeout.connect(on_timeout)
timer.start(1 * 1000)
on_timeout()
app.exec_()
You should not use while loop or time.sleep since they block the eventloop in which the GUI lives, that is: they freeze the windows
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 :-)