Updating GUI elements in MultiThreaded PyQT
Solution 1
Here some very basic examples.
You can pass references to GUI elements to threads, and update them in thread.
import sys
import urllib2
from PyQt4 import QtCore, QtGui
class DownloadThread(QtCore.QThread):
def __init__(self, url, list_widget):
QtCore.QThread.__init__(self)
self.url = url
self.list_widget = list_widget
def run(self):
info = urllib2.urlopen(self.url).info()
self.list_widget.addItem('%s\n%s' % (self.url, info))
class MainWindow(QtGui.QWidget):
def __init__(self):
super(MainWindow, self).__init__()
self.list_widget = QtGui.QListWidget()
self.button = QtGui.QPushButton("Start")
self.button.clicked.connect(self.start_download)
layout = QtGui.QVBoxLayout()
layout.addWidget(self.button)
layout.addWidget(self.list_widget)
self.setLayout(layout)
def start_download(self):
urls = ['http://google.com', 'http://twitter.com', 'http://yandex.ru',
'http://stackoverflow.com/', 'http://www.youtube.com/']
self.threads = []
for url in urls:
downloader = DownloadThread(url, self.list_widget)
self.threads.append(downloader)
downloader.start()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = MainWindow()
window.resize(640, 480)
window.show()
sys.exit(app.exec_())
Editors Note: Qt widgets are not thread safe and should not be accessed from any thread but the main thread (see the Qt documentation for more details). The correct way to use threads is via signals/slots as the second part of this answer shows.
Also, you can use signals and slots, to separate gui and network logic.
import sys
import urllib2
from PyQt4 import QtCore, QtGui
class DownloadThread(QtCore.QThread):
data_downloaded = QtCore.pyqtSignal(object)
def __init__(self, url):
QtCore.QThread.__init__(self)
self.url = url
def run(self):
info = urllib2.urlopen(self.url).info()
self.data_downloaded.emit('%s\n%s' % (self.url, info))
class MainWindow(QtGui.QWidget):
def __init__(self):
super(MainWindow, self).__init__()
self.list_widget = QtGui.QListWidget()
self.button = QtGui.QPushButton("Start")
self.button.clicked.connect(self.start_download)
layout = QtGui.QVBoxLayout()
layout.addWidget(self.button)
layout.addWidget(self.list_widget)
self.setLayout(layout)
def start_download(self):
urls = ['http://google.com', 'http://twitter.com', 'http://yandex.ru',
'http://stackoverflow.com/', 'http://www.youtube.com/']
self.threads = []
for url in urls:
downloader = DownloadThread(url)
downloader.data_downloaded.connect(self.on_data_ready)
self.threads.append(downloader)
downloader.start()
def on_data_ready(self, data):
print data
self.list_widget.addItem(unicode(data))
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = MainWindow()
window.resize(640, 480)
window.show()
sys.exit(app.exec_())
Solution 2
While reclosedev's answer worked perfectly for me, I was getting a QThread: Destroyed while thread is still running
error. To correct this, I passed the parent class reference to the QThread constructor, as indicated in this question.
import sys
import urllib2
from PyQt4 import QtCore, QtGui
class DownloadThread(QtCore.QThread):
data_downloaded = QtCore.pyqtSignal(object)
def __init__(self, parent, url):
QtCore.QThread.__init__(self, parent)
self.url = url
def run(self):
info = urllib2.urlopen(self.url).info()
self.data_downloaded.emit('%s\n%s' % (self.url, info))
class MainWindow(QtGui.QWidget):
def __init__(self):
super(MainWindow, self).__init__()
self.list_widget = QtGui.QListWidget()
self.button = QtGui.QPushButton("Start")
self.button.clicked.connect(self.start_download)
layout = QtGui.QVBoxLayout()
layout.addWidget(self.button)
layout.addWidget(self.list_widget)
self.setLayout(layout)
def start_download(self):
urls = ['http://google.com', 'http://twitter.com', 'http://yandex.ru',
'http://stackoverflow.com/', 'http://www.youtube.com/']
self.threads = []
for url in urls:
downloader = DownloadThread(parent=self, url)
downloader.data_downloaded.connect(self.on_data_ready)
self.threads.append(downloader)
downloader.start()
def on_data_ready(self, data):
print data
self.list_widget.addItem(unicode(data))
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = MainWindow()
window.resize(640, 480)
window.show()
sys.exit(app.exec_())
Related videos on Youtube
Nuncjo
Updated on November 18, 2021Comments
-
Nuncjo over 2 years
I was researching for some time to find information how to do multithreaded program using PyQT, updating GUI to show the results.
I'm used to learning by example and i can't find (yes i was looking for weeks) any simple example of program using multithreading doing such simple task as for example connecting to list of www sites (5 threads) and just printing processed urls with response code.
Could anyone share code or send me to good tutorial where such program is explained ?
-
Froyo about 12 yearshey, I haven't tried pyQt, but I have used multithreading in pygtk. In pygtk, gobject is generally used for doing that. You should search for something similar for pyQt.
-
Trilarion over 9 years
-
-
Nuncjo about 12 yearsI will analyze this examples. Thank You.
-
Whatang about 12 years+1 for suggesting using signals to separate the threading from the display logic.
-
Nuncjo about 12 yearsExamples are great that's what i was looking for but how to limit threads number to 5 working simultaneously ?
-
reclosedev about 12 years@MikeJanson, take a look at
QThreadPool
and itsmaxThreadCount
property -
Nuncjo about 12 yearsUhmm.. i'm sorry but im to green in C++ to translate this to Python. That's one of the reason of looking the answer in stackoverflow.
-
reclosedev about 12 years@MikeJanson, maybe PySide documentation will be simplier QThreadPool
-
Nuncjo about 12 yearsHmm ok i understood something. I'm changing QThread to QRunnable in second example, then trying to start it by QtCore.QThreadPool.globalInstance().start(downloader) gives me TypeError: pyqtSignal must be bound to a QObject, not 'DownloadThread'
-
reclosedev about 12 years@MikeJanson, Yes, I've tried to write example of using
QThreadPool
and encountered the same problem. Your class should inheritQObject
to use signals (QRunable
- doesn't), but thenQThreadPool
will not accept it. Here's example with workaround: pastebin.com/npnwenYw -
Nuncjo about 12 yearsI found a possible solution (kedeligdata.blogspot.com/2010/01/…) but can't put it in this example.
-
Nuncjo about 12 yearsI didn't notice earlier, Your workaround also works great. Now finally i can do my own multithreaded python apps using GUI. Thanks for help.
-
Fabrizio almost 11 yearsThere are really a lot of examples (maybe not for PyQT) about this but as the many many examples out there this example as well will allow to print the output of the thread only after the urllib2 call has ended. This is a big limitation in my opinion. Not that in the sense that it does not work but in the sense that a more realistic implementation would need data flow stuff (e.g. pipe), and this is more complicated.
-
Winand about 9 yearsBut it's forbidden to interact with gui directly from non-gui(main) thread, isn't it?
-
Aaron Digulla about 9 yearsNote: The first example is wrong. You're not allowed to call
QListWidget.addItem()
from a thread. You have to use signals. -
three_pineapples almost 9 yearsYep, this is a terrible answer (sorry). If you do this, your program will randomly crash. Qt widgets are not thread safe...
-
reclosedev almost 9 years@three_pineapples, thanks for edit! I agree about widgets and thread safety. That code worked because of luck on specific version, I guess.
-
Josh Usre about 8 yearsHow would I adapt this for multiple workers with multiple enumerate gui elements/