Collectives™ on Stack Overflow

Find centralized, trusted content and collaborate around the technologies you use most.

Learn more about Collectives

Teams

Q&A for work

Connect and share knowledge within a single location that is structured and easy to search.

Learn more about Teams

Below is my code for listing all the sub-directories of a directory. I'm using it to understand QThread and signal and slots in PySide. The problem is, when I'm not using Qtcore.QApplication.processEvents() in the scan() method of the Main class, the code does not work. Is the event-loop not already running?

import sys
import os
import time
from PySide import QtGui, QtCore
class Scanner(QtCore.QObject):
    folderFound = QtCore.Signal(str)
    done = QtCore.Signal()
    def __init__(self, path):
        super(Scanner, self).__init__()
        self.path = path
    @QtCore.Slot()
    def scan(self):
        for folder in os.listdir(self.path):
            time.sleep(1)
            self.folderFound.emit(folder)
class Main(QtGui.QWidget):
    def __init__(self):
        QtGui.QWidget.__init__(self)
        self.resize(420,130)
        self.setWindowTitle('Threads')
        self.lyt = QtGui.QVBoxLayout()
        self.setLayout(self.lyt)
        self.topLyt = QtGui.QHBoxLayout()
        self.lyt.addLayout(self.topLyt)
        self.scanBtn = QtGui.QPushButton('Scan')
        self.scanBtn.clicked.connect(self.scan)
        self.clearBtn = QtGui.QPushButton('Clear')
        self.topLyt.addWidget(self.scanBtn)
        self.topLyt.addWidget(self.clearBtn)
        self.folders = list()
        self.show()
    def scan(self):
        self.th = QtCore.QThread()
        scanner = Scanner(r"D:\\")
        scanner.moveToThread(self.th)
        scanner.folderFound.connect(self.addFolder)
        scanner.done.connect(scanner.deleteLater)
        scanner.done.connect(self.quit)
        self.th.started.connect(scanner.scan)
        self.th.start()
        QtCore.QApplication.processEvents()
    @QtCore.Slot()
    def addFolder(self, folder):
        lbl = QtGui.QLabel(folder)
        self.folders.append(lbl)
        self.lyt.addWidget(lbl)
    @QtCore.Slot()
    def quit(self):
        self.th.quit()
        self.th.wait()
if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    main = Main()    
    app.exec_()
                It  means when I click 'Scan' button, nothing happens, but when I use processEvents, It starts to add folder names in label which it is getting from the thread.
– Vicspidy
                Feb 15, 2018 at 7:38

It is a pure fluke that your example works at all.

When you attempt to call QtCore.QApplication.processEvents(), a NameError will be raised, because the QApplication class is actually in the QtGui module, not the QtCore module. However, raising the exception has the side-effect of preventing your scanner object from being garbage-collected, and so the thread appears to run normally.

The correct way to fix your code is to keep a reference to the scanner object, and get rid of the processEvents line, which is not needed:

def scan(self):
    self.th = QtCore.QThread()
    # keep a reference to the scanner
    self.scanner = Scanner(r"D:\\")
    self.scanner.moveToThread(self.th)
    self.scanner.folderFound.connect(self.addFolder)
    self.scanner.done.connect(self.scanner.deleteLater)
    self.scanner.done.connect(self.quit)
    self.th.started.connect(self.scanner.scan)
    self.th.start()

The rest of your code is okay, and the example will now work as expected.

Event loop is not something that runs behind your back. You're always the one who has to run it. A thread cannot be doing two things at once: if your code is the locus of control, then obviously the event loop isn't! You need to return from your code to the event loop, and make sure that your code was called from the event loop. Any sort of a UI signal, networking event, or timeout is invoked from the event loop, so most likely your code already has the event loop on the call stack. To keep the loop spinning, you have to return to it, though.

Never use processEvents - instead, invert the control, so that the event loop calls into your code, and then you perform a chunk of work, and finally return back to the event loop.

The idiom for "keep my code working from event loop" is a zero-duration timer. The callable that performs the work is attached to the timeout signal.

Thanks for contributing an answer to Stack Overflow!

  • Please be sure to answer the question. Provide details and share your research!

But avoid

  • Asking for help, clarification, or responding to other answers.
  • Making statements based on opinion; back them up with references or personal experience.

To learn more, see our tips on writing great answers.