from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import time
import sys
import numpy as np
class Mainthread(QThread):
def __init__(self, parent):
super().__init__(parent)
self.parent = parent
self.running = None
self.mutex = QMutex()
def run(self):
while self.running:
self.mutex.lock()
print ("test")
time.sleep(1)
self.mutex.unlock()
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.mainthread = Mainthread(self)
self.mainthread.running = True
self.mainthread.start()
self.mainthread1 = Mainthread(self)
self.mainthread1.running = True
self.mainthread1.start()
app = QApplication(sys.argv)
mainwindow = MainWindow()
mainwindow.show()
app.exec_()
I have this code where I run two instances of the same MainThread class.
What I was expecting was that mainthread's message (which is "test") would print, then wait for a sec and then mainthread1's would be printed. Instead, it seems like both threads are running at the same time. Is there something I'm missing?
In your code, each thread creates its own separate mutex, so no relation is enforced between the two. Create a single mutex first, and pass it to the theads:
import time
import sys
from PyQt5.QtCore import QThread, QMutex
from PyQt5.QtWidgets import QMainWindow, QApplication
class Mainthread(QThread):
def __init__(self, mutex, parent):
super().__init__(parent)
self.parent = parent
self.running = None
self.mutex = mutex
def run(self):
while self.running:
self.mutex.lock()
print ("test")
time.sleep(1)
self.mutex.unlock()
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
mutex = QMutex()
self.mainthread = Mainthread(mutex, self)
self.mainthread.running = True
self.mainthread.start()
self.mainthread1 = Mainthread(mutex, self)
self.mainthread1.running = True
self.mainthread1.start()
app = QApplication(sys.argv)
mainwindow = MainWindow()
mainwindow.show()
app.exec_()
Note: I don't have PyQt5 installed (and doing so on my architecture is tricky), but I tested this in PySide6 and as far as I know the behavior should be consistent.
Related
I have the following code but it's complaining that I cannot access the UI data from my thread. In my example code below, What is the best way I can access the userInputString value so my threading can run?
self.nameField is a PyQt QLineEdit.
QObject::setParent: Cannot set parent, new parent is in a different thread
QPixmap: It is not safe to use pixmaps outside the GUI thread
QWidget::repaint: Recursive repaint detected
import myUI
class MainUIClass(QtGui.QMainWindow, myUI.Ui_MainWindow):
def __init__(self, parent=None):
super(MainUIClass, self).__init__(parent)
self.setupUi(self)
self.startbutton.clicked.connect(self.do_work)
self.workerThread = WorkerThread()
self.connect(self.workerThread, SIGNAL("myThreading()"), self.myThreading, Qt.DirectConnection)
def do_work(self):
self.userInputString = self.nameField.Text()
self.workerThread.start()
def myThreading(self):
if userInputString is not None:
#Do something
class WorkerThread(QThread):
def __init__(self, parent=None):
super(WorkerThread, self).__init__(parent)
def run(self):
self.emit(SIGNAL("myThreading()"))
if __name__ == '__main__':
a = QtGui.QApplication(sys.argv)
app = MainUIClass()
app.show()
a.exec_()
Not sure if it's what you need but here is a working QThread exemple using Qt5
import time
import sys
from PyQt5 import QtWidgets, QtGui, QtCore
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.worker_thread = WorkerThread()
self.worker_thread.job_done.connect(self.on_job_done)
self.create_ui()
def create_ui(self):
self.button = QtWidgets.QPushButton('Test', self)
self.button.clicked.connect(self.start_thread)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.button)
def start_thread(self):
self.worker_thread.gui_text = self.button.text()
self.worker_thread.start()
def on_job_done(self, generated_str):
print("Generated string : ", generated_str)
self.button.setText(generated_str)
class WorkerThread(QtCore.QThread):
job_done = QtCore.pyqtSignal('QString')
def __init__(self, parent=None):
super(WorkerThread, self).__init__(parent)
self.gui_text = None
def do_work(self):
for i in range(0, 1000):
print(self.gui_text)
self.job_done.emit(self.gui_text + str(i))
time.sleep(0.5)
def run(self):
self.do_work()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
test = MainWindow()
test.show()
app.exec_()
I'm having a hard time finding a way to reference class instances in a decorator function.
import json
import time
import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from main_UI import Ui_ApplicationWindow
from slack import RTMClient
class WorkerThread(QThread):
finished = pyqtSignal(str)
def __init__(self):
QThread.__init__(self)
self.rtm_client = RTMClient(token="xoxp...")
def run(self):
self.rtm_client.start()
#RTMClient.run_on(event="message")
def say_hello(**payload):
data = payload['data']
if (len(data) != 0):
if "text" in data:
text = data['text']
self.finished.emit(str(text))
class ApplicationWindow(QMainWindow):
def __init__(self):
super(ApplicationWindow, self).__init__()
self.ui = Ui_ApplicationWindow()
self.ui.setupUi(self)
self.ui.pushButton.clicked.connect(self.start_rtm)
def start_rtm(self):
self.thread = WorkerThread()
self.thread.finished.connect(self.update)
self.thread.start()
#pyqtSlot(str)
def update(self, data):
self.ui.label.setText(data)
if __name__ == "__main__":
app = QApplication(sys.argv)
myWindow = ApplicationWindow()
myWindow.show()
app.exec_()
So in say_hello since it can't take self as an argument, I'm not able to use self.finished.emit(text) at the end of the function.
How can I reference a class instance/function using self in say_hello?
No, You can not. Instead of using the #RTMClient.run_on() decorator, use the RTMClient.on() function to register it.
import threading
import asyncio
from slack import RTMClient
from PyQt5 import QtCore, QtWidgets
class SlackClient(QtCore.QObject):
textChanged = QtCore.pyqtSignal(str)
def start(self):
RTMClient.on(event="message", callback=self.say_hello)
threading.Thread(target=self._start_loop, daemon=True).start()
def _start_loop(self):
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
slack_token = "xoxb-...."
rtm_client = RTMClient(token=slack_token)
rtm_client.start()
def say_hello(self, **payload):
data = payload["data"]
if data:
if "text" in data:
text = data["text"]
self.textChanged.emit(text)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
client = SlackClient()
button = QtWidgets.QPushButton("Start")
textedit = QtWidgets.QPlainTextEdit()
button.clicked.connect(client.start)
client.textChanged.connect(textedit.appendPlainText)
w = QtWidgets.QWidget()
lay = QtWidgets.QVBoxLayout(w)
lay.addWidget(button)
lay.addWidget(textedit)
w.show()
sys.exit(app.exec_())
Update:
import sys
import threading
import asyncio
from slack import RTMClient
from PyQt5 import QtCore, QtWidgets
from main_UI import Ui_ApplicationWindow
class SlackClient(QtCore.QObject):
textChanged = QtCore.pyqtSignal(str)
def start(self):
RTMClient.on(event="message", callback=self.say_hello)
threading.Thread(target=self._start_loop, daemon=True).start()
def _start_loop(self):
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
slack_token = "xoxb-...."
rtm_client = RTMClient(token=slack_token)
rtm_client.start()
def say_hello(self, **payload):
data = payload["data"]
if data:
if "text" in data:
text = data["text"]
self.textChanged.emit(text)
class ApplicationWindow(QtWidgets.QMainWindow):
def __init__(self):
super(ApplicationWindow, self).__init__()
self.ui = Ui_ApplicationWindow()
self.ui.setupUi(self)
self.client = SlackClient()
# connections
self.ui.pushButton.clicked.connect(self.client.start)
self.client.textChanged.connect(self.ui.label.setText)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
myWindow = ApplicationWindow()
myWindow.show()
sys.exit(app.exec_())
Actually you cannot self since that is a global variable, not a class one.
from slack import RTMClient
class WorkerThread(QThread):
finished = pyqtSignal(dict)
def __init__(self):
QThread.__init__(self)
self.rtm_client = RTMClient(token="xoxp-....")
def run(self):
self.rtm_client.start()
#RTMClient.run_on(event="message")
def say_hello(**payload):
data = payload['data']
if (len(data) != 0):
if "text" in data:
text = data['text']
WorkerThread.finished.emit(text) <--- using self impossible
I suggest that you make such variable private by appending two underscores at the beginning (__my_private_var)
Here is some sample code that breaks:
import sys
import time
from PyQt5.QtWidgets import (QApplication, QDialog,
QProgressBar)
class Actions(QDialog):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.progress = QProgressBar(self)
self.progress.setGeometry(0, 0, 300, 25)
self.show()
self.count = 0
while self.count < 100:
self.count += 1
time.sleep(1) # Example external function
self.progress.setValue(self.count)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = Actions()
sys.exit(app.exec_())
Running this will cause it to freeze and become unresponsive particularly in windows environments. Replacing the time.sleep function with any non-PyQt5 function will yield the same results.
From what I understand this has to do with the function not being called in a separate thread using QThread. I used this answer as a reference and came up with a partial solution.
import sys
import time
from PyQt5.QtCore import QThread
from PyQt5.QtWidgets import (QApplication, QDialog,
QProgressBar)
class External(QThread):
def run(self):
count = 0
while count < 100:
count += 1
print(count)
time.sleep(1)
class Actions(QDialog):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.progress = QProgressBar(self)
self.progress.setGeometry(0, 0, 300, 25)
self.show()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = Actions()
calc = External()
calc.finished.connect(app.exit)
calc.start()
sys.exit(app.exec_())
This will run time.sleep in the background and keep the main window responsive. But, I don't know how to update the values using self.progress.setValue since it's not accessible in class External.
So far from what I know, I have to use signals to accomplish this. Most of the documentation out there is for PyQt4 making it harder to find a solution.
Another problem I am faced with is being able to start the External thread from within class Actions.
Answers to this problem will also serve as valuable documentation for PyQt5.
Thanks in advance.
You must use the signals to update the values.
import sys
import time
from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.QtWidgets import (QApplication, QDialog,
QProgressBar)
class External(QThread):
countChanged = pyqtSignal(int)
def run(self):
count = 0
while count < 100:
count += 1
self.countChanged.emit(count)
print(count)
time.sleep(1)
class Actions(QDialog):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.progress = QProgressBar(self)
self.progress.setGeometry(0, 0, 300, 25)
self.show()
def onCountChanged(self, value):
self.progress.setValue(value)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = Actions()
calc = External()
calc.countChanged.connect(window.onCountChanged)
calc.start()
sys.exit(app.exec_())
Here is a version that starts the thread from inside class Actions and uses a button:
import sys
import time
from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.QtWidgets import (QApplication, QDialog,
QProgressBar, QPushButton)
class External(QThread):
countChanged = pyqtSignal(int)
def run(self):
count = 0
while count < 100:
count += 1
time.sleep(1)
print(count)
self.countChanged.emit(count)
class Actions(QDialog):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.progress = QProgressBar(self)
self.progress.setGeometry(0, 0, 300, 25)
self.button = QPushButton('Start', self)
self.button.move(0, 30)
self.show()
self.button.clicked.connect(self.onButtonClick)
def onButtonClick(self):
self.calc = External()
self.calc.countChanged.connect(self.onCountChanged)
self.calc.start()
def onCountChanged(self, value):
self.progress.setValue(value)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = Actions()
sys.exit(app.exec_())
I'm trying to write a pyqt5 application with a long running, but not CPU intensive process. I'd like to be able to run it without hanging the UI, so I'm trying to use threading, but since it doesn't seem like I can just run a thread and have it stop after its gone through its code so that it can be run again, I've tried setting up the thread to wait for a variable to change before running.
I know this can't be the correct pattern for running long processes in a pyqt app.
import time
import threading
from PyQt5 import QtWidgets, uic
class MyApp(QtWidgets.QMainWindow):
_run_thread = False
def __init__(self):
QtWidgets.QMainWindow.__init__(self)
self.ui = uic.loadUi('myapp.ui', self)
self.ui.start_thread_button.clicked.connect(self._run_thread_function)
self._thread = threading.Thread(target=self._run_thread_callback)
self._thread.daemon = True
self._thread.start()
self.ui.show()
def _run_thread_callback(self):
while True:
if self._run_thread:
print("running thread code...")
time.sleep(10)
print("thread code finished")
self._run_thread = False
def _run_thread_function(self):
print("starting thread...")
self._run_thread = True
def main():
app = QtWidgets.QApplication(sys.argv)
MyApp()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Below is a simple demo showing how to start and stop a worker thread, and safely comminucate with the gui thread.
import sys
from PyQt5 import QtCore, QtWidgets
class Worker(QtCore.QThread):
dataSent = QtCore.pyqtSignal(dict)
def __init__(self, parent=None):
super(Worker, self).__init__(parent)
self._stopped = True
self._mutex = QtCore.QMutex()
def stop(self):
self._mutex.lock()
self._stopped = True
self._mutex.unlock()
def run(self):
self._stopped = False
for count in range(10):
if self._stopped:
break
self.sleep(1)
data = {
'message':'running %d [%d]' % (
count, QtCore.QThread.currentThreadId()),
'time': QtCore.QTime.currentTime(),
'items': [1, 2, 3],
}
self.dataSent.emit(data)
class Window(QtWidgets.QWidget):
def __init__(self):
super(Window, self).__init__()
self.edit = QtWidgets.QPlainTextEdit()
self.edit.setReadOnly(True)
self.button = QtWidgets.QPushButton('Start')
self.button.clicked.connect(self.handleButton)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.edit)
layout.addWidget(self.button)
self._worker = Worker()
self._worker.started.connect(self.handleThreadStarted)
self._worker.finished.connect(self.handleThreadFinished)
self._worker.dataSent.connect(self.handleDataSent)
def handleThreadStarted(self):
self.edit.clear()
self.button.setText('Stop')
self.edit.appendPlainText('started')
def handleThreadFinished(self):
self.button.setText('Start')
self.edit.appendPlainText('stopped')
def handleDataSent(self, data):
self.edit.appendPlainText('message [%d]' %
QtCore.QThread.currentThreadId())
self.edit.appendPlainText(data['message'])
self.edit.appendPlainText(data['time'].toString())
self.edit.appendPlainText(repr(data['items']))
def handleButton(self):
if self._worker.isRunning():
self._worker.stop()
else:
self._worker.start()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.setGeometry(500, 100, 400, 400)
window.show()
sys.exit(app.exec_())
I have 2 QListWidget lists, List2 is being filled when some item has been selected from List1
The problem is that, before filling List2 i have to do alot of tasks which freezes my UI for about 5 seconds which is too annoying, i want to make it fill List2 with QThread but it's not working since before initilizing whole class I'm getting an annoying error
from ui import Ui_Win
from PyQt4 import QtGui, QtCore
class GenericThread(QtCore.QThread):
def __init__(self, parent=None):
QtCore.QThread.__init__(self, parent)
def __del__(self):
self.quit()
self.wait()
def run(self):
self.emit( QtCore.SIGNAL('itemSelectionChanged()'))
return
class MainUI(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QMainWindow.__init__(self)
self.ui = Ui_Win()
self.ui.setupUi(self)
...
genericThread = GenericThread(self)
self.connect(genericThread, QtCore.SIGNAL("itemSelectionChanged()"), self.fill_List2 )
genericThread.start()
def fill_List2(self):
self.ui.List2.clear()
list1SelectedItem = str(self.ui.List1.currentItem().text()) # ERROR HERE
Traceback:
# AttributeError: 'NoneType' object has no attribute 'text'
This accures because self.ui.List1.currentItem().text() is None
Why this fucntion is being called before triggering itemSelectionChanged signal?
from ui import Ui_Win ## ui.py is a file that has been generated from Qt Designer and it contains main GUI objects like QListWidget
from PyQt4 import QtGui, QtCore
class GenericThread(QtCore.QThread):
def __init__(self, parent=None, listIndex=0):
QtCore.QThread.__init__(self, parent)
self.listIndex = listIndex
def __del__(self):
self.quit()
self.wait()
def run(self):
if self.listIndex == 2:
for addStr in Something:
#Some long stuff
self.emit( QtCore.SIGNAL('fillListWithItems(QString, int'), addStr, self.listIndex)
class MainUI(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QMainWindow.__init__(self)
self.ui = Ui_Win()
self.ui.setupUi(self)
...
self.ui.List1.list1SelectedItem.connect(self.fill_List2)
#QtCore.pyqtSlot(QString, int)
def self.fillListWithItems(addStr, lstIdx):
if lstIdx==2:
self.ui.List2.addItem(addStr)
def fill_List2(self):
self.ui.List2.clear()
list1SelectedItem = str(self.ui.List1.currentItem().text())
genericThread = GenericThread(self, listIndex=2)
self.connect(genericThread, QtCore.SIGNAL("fillListWithItems(QString, int)"), self.fillListWithItems )
genericThread.start()
Thanks #ekhumoro
Your problem is that simply starting the thread is firing the itemSelectionChanged() signal (because it is in the run function), which you have connected to your fill_List2() function. You need to connect the itemSelectionChanged event on List1 to a SLOT that will trigger the thread to do the heavy computation and update List2.
I've had to make a number of assumptions about what ui is and what List1 and List2 are and how they're set up, but here is a working example. I've replaced the heavy computation in your GenericThread with a simple 2 second delay.
If I've misinterpreted / made bad assumptions, please update the question and leave a comment
test_slotting.py
from ui import Ui_Win
from PyQt4 import QtGui, QtCore
from PyQt4.QtCore import pyqtSlot
from PyQt4.QtGui import *
import time
class GenericThread(QtCore.QThread):
def __init__(self, parent=None):
QtCore.QThread.__init__(self, parent)
def __del__(self):
self.quit()
self.wait()
def run(self):
#Do all your heavy processing here
#I'll just wait for 2 seconds
time.sleep(2)
self.emit( QtCore.SIGNAL('itemSelectionChanged()'))
return
class MainUI(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QMainWindow.__init__(self)
self.ui = Ui_Win()
self.ui.setupUi(self)
self.ui.List1 = QListWidget(self)
self.ui.List2 = QListWidget(self)
hbox = QtGui.QHBoxLayout()
hbox.addStretch(1)
hbox.addWidget(self.ui.List1)
hbox.addWidget(self.ui.List2)
self.ui.centralWidget.setLayout(hbox)
self.ui.List1.addItems(['alpha','beta','gamma','delta','epsilon'])
self.ui.List2.addItems(['Item1','Item2'])
self.ui.List1.itemSelectionChanged.connect(self.start_heavy_processing_thread)
#pyqtSlot()
def start_heavy_processing_thread(self):
genericThread = GenericThread(self)
self.connect(genericThread, QtCore.SIGNAL("itemSelectionChanged()"), self.fill_List2 )
genericThread.start()
def fill_List2(self):
self.ui.List2.clear()
list1SelectedItem = str(self.ui.List1.currentItem().text())
self.ui.List2.addItem(list1SelectedItem)
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
MainWindow = MainUI()
MainWindow.show()
sys.exit(app.exec_())
ui.py
from PyQt4 import QtCore, QtGui
class Ui_Win(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(416, 292)
self.centralWidget = QtGui.QWidget(MainWindow)
self.centralWidget.setObjectName("centralWidget")
MainWindow.setCentralWidget(self.centralWidget)
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
MainWindow = QtGui.QMainWindow()
ui = Ui_Win()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())