28

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 ?

Nuncjo
  • 1,290
  • 3
  • 15
  • 16
  • 1
    hey, 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. – Froyo Mar 31 '12 at 16:36
  • see also http://stackoverflow.com/questions/11265812/pyside-pyqt-starting-a-cpu-intensive-thread-hangs-the-whole-application, http://stackoverflow.com/questions/16879971/example-of-the-right-way-to-use-qthread-in-pyqt, http://stackoverflow.com/questions/6783194/background-thread-with-qthread-in-pyqt or http://stackoverflow.com/questions/20752154/pyqt-connecting-a-signal-to-a-slot-to-start-a-background-operation – NoDataDumpNoContribution Jan 07 '15 at 09:44

2 Answers2

55

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_())
three_pineapples
  • 11,579
  • 5
  • 38
  • 75
reclosedev
  • 9,352
  • 34
  • 51
  • I will analyze this examples. Thank You. – Nuncjo Apr 01 '12 at 13:28
  • 8
    +1 for suggesting using signals to separate the threading from the display logic. – Whatang Apr 02 '12 at 00:23
  • Examples are great that's what i was looking for but how to limit threads number to 5 working simultaneously ? – Nuncjo Apr 07 '12 at 13:55
  • @MikeJanson, take a look at [`QThreadPool`](http://qt-project.org/doc/qt-4.8/qthreadpool.html) and its [`maxThreadCount` property](http://qt-project.org/doc/qt-4.8/qthreadpool.html#maxThreadCount-prop) – reclosedev Apr 07 '12 at 14:13
  • Uhmm.. 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. – Nuncjo Apr 07 '12 at 14:48
  • @MikeJanson, maybe PySide documentation will be simplier [QThreadPool](http://www.pyside.org/docs/pyside/PySide/QtCore/QThreadPool.html) – reclosedev Apr 07 '12 at 14:51
  • Hmm 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' – Nuncjo Apr 07 '12 at 15:19
  • @MikeJanson, Yes, I've tried to write example of using `QThreadPool` and encountered the same problem. Your class should inherit `QObject` to use signals (`QRunable` - doesn't), but then `QThreadPool` will not accept it. Here's example with workaround: http://pastebin.com/npnwenYw – reclosedev Apr 07 '12 at 15:31
  • I found a possible solution (http://kedeligdata.blogspot.com/2010/01/pyqt-emitting-events-from-non-qobject.html) but can't put it in this example. – Nuncjo Apr 09 '12 at 09:14
  • I didn't notice earlier, Your workaround also works great. Now finally i can do my own multithreaded python apps using GUI. Thanks for help. – Nuncjo Apr 09 '12 at 09:57
  • There 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. – Fabrizio Aug 06 '13 at 16:46
  • 1
    But it's forbidden to interact with gui directly from non-gui(main) thread, isn't it? – Winand Apr 08 '15 at 14:28
  • 3
    Note: The first example is wrong. You're not allowed to call `QListWidget.addItem()` from a thread. You have to use signals. – Aaron Digulla Apr 15 '15 at 15:17
  • 1
    Yep, this is a terrible answer (sorry). If you do this, your program will randomly crash. Qt widgets are not thread safe... – three_pineapples Jul 27 '15 at 04:58
  • 2
    @three_pineapples, thanks for edit! I agree about widgets and thread safety. That code worked because of luck on specific version, I guess. – reclosedev Jul 27 '15 at 19:16
  • 1
    How would I adapt this for multiple workers with multiple enumerate gui elements/ – Josh Usre Mar 23 '16 at 18:36
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_())
Lucas Franceschi
  • 398
  • 6
  • 12