pythonmultithreadingqtpyqtsignals-slots

Get notification when QThreadPool of QRunners in PyQt4 have completed execution


I've got this quick and dirty test I setup where I'm running a thread of QRunners in a QThreadPool one by one in PyQt4 on Python 2.7. Basically it looks like it's running fine, but the threads/pool don't seem to stop the QThreadPool once all threads have completed execution.

I'm wondering if it's as simple as returning some built in method from the QRunnable (ProductImporter here) when it's done executing it's code, but I can't seem to find anything in the documentation.

I'm moving some code over to this setup from a QThread structure, since I can't have concurrent threads running at the same time.

Any ideas on how I could have the ProductImportTasks aware that it's tasks have all completed, so I can execute more code afterwards, or after that class is called? Any help would be greatly appreciated!

import sys
from PyQt4.QtGui import QApplication
from PyQt4.QtCore import QThreadPool, QObject, QRunnable, pyqtSignal

class WorkerSignals(QObject):
    productResult = pyqtSignal(str)

class ProductImporter(QRunnable):
    def __init__(self, product):
        super(ProductImporter, self).__init__()

        self.product = product
        self.signals = WorkerSignals()

    def run(self):
        self.signals.productResult.emit(self.product['name'])
        return

class ProductImportTasks(QObject):
    def __init__(self, products):
        super(ProductImportTasks, self).__init__()
        self.products = products
        self.pool = QThreadPool()
        self.pool.setMaxThreadCount(1)

    def process_result(self, product):
        return

    def start(self):
        for product in self.products:
            worker = ProductImporter(product)
            worker.signals.productResult.connect(view.text)
            self.pool.start(worker)

        self.pool.waitForDone()


class ViewController(QObject):
    def __init__(self, parent=None):
        super(ViewController, self).__init__(parent)

    #@pyqtSlot(str)
    def text(self, message):
        print "This is the view.text method: " + message
        return


if __name__ == "__main__":

    app = QApplication(sys.argv)
    view = ViewController()
    main = ProductImportTasks([{"name": "test1"}, {"name": "test2"}, {"name": "test3"}])
    main.start()
    sys.exit(app.exec_())

Solution

  • Here's what your script does:

    1. calls main.start()
    2. creates runnables and starts threadpool
    3. waits for all the runnables to finish
    4. returns from main.start()
    5. starts the application event-loop

    Once the event-loop has started, the signals that were emitted by the runnables will be processed, and the messages will be printed. This is because signals sent across thread are queued by default. Normally, signals are sent synchronously, and don't require a running event loop.

    If you change the connection type of the signals, and add a few print statements, it should be clear what's going on:

                worker.signals.productResult.connect(view.text, Qt.DirectConnection)
                self.pool.start(worker)
    
            self.pool.waitForDone()
            print('finished')
            ...
    
        main.start()
        print('exec')
        sys.exit(app.exec_())