Search code examples
pythonmultithreadingpyqt5qthread

PyQt5 black window while threads are working


I am making window application in PyQt5 and I want to parse data from XML file in the backgrond and send it to second class. I want to use threads with queue to handle between them. When I want to display window app i see black window. It would be nice to use python threads but i tried to do it on QThread and it is not working too idk why... This is code example

import queue
import sys
import threading

from PyQt5.QtCore import QThread, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import *


class Test_generator:
    def __init__(self, queue_obj):
        super().__init__()
        self.queue_obj = queue_obj
        #self.parser_thread = threading.Thread(target=self.parser())

    def sender(self):
        for _ in range(100):
            string ="test"
            self.queue_obj.put(string)# send to queue
            time.sleep(1)
            #print(string)


class Test_collectioner:
    def __init__(self,queue_obj):
        self.queue_obj = queue_obj

    def get_data(self):
        collection = []
        while self.queue_obj.empty() is False:
            print("xd")
            print(self.queue_obj.get(), "xd")


#I found this example in the internet(Not working)
class Threaded(QThread):
    result = pyqtSignal(int)

    def __init__(self, parent=None, **kwargs):
        super().__init__(parent, **kwargs)

    @pyqtSlot(int)
    def run(self):
        while True:
            print("test")


class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.GUI()
        self.setWindowTitle("PyQt5 app")

        global q
        q = queue.Queue()

        # BLACKSCREEN (for Test_generator)
        test_generator_obj = Test_generator(q)
        test_collectioner_obj = Test_collectioner(q)

        t1 = threading.Thread(target=test_generator_obj.sender())
        t2 = threading.Thread(target=test_collectioner_obj.get_data())
        t1.start()
        t2.start()



        # BLACKSCREEN TOO
        """self.thread = QThread()
        self.threaded = Threaded()
        self.thread.started.connect(self.threaded.run())
        self.threaded.moveToThread(self.thread)
        qApp.aboutToQuit.connect(self.thread.quit)
        self.thread.start()"""



    def GUI(self):
        self.showMaximized()



if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MainWindow()
    sys.exit(app.exec_())

Solution

  • There are two main problems in your code:

    1. you're executing the methods used for the thread, but you should use the callable object instead; you did the same mistake for the QThread, since connections to signals also expects a callable, but you're actually executing the run function from the main thread (completely blocking everything), since you're using the parentheses; those lines should be like this: t1 = threading.Thread(target=test_generator_obj.sender)
      t2 = threading.Thread(target=test_collectioner_obj.get_data)
      or, for the QThread:
      self.thread.started.connect(self.threaded.run)

    2. due to the python GIL, multithreading only releases control to the other threads whenever it allows to, but the while cycle you're using prevents that; adding a small sleep function ensures that control is periodically returned to the main thread;

    Other problems also exist:

    • you're already using a subclass of QThread, so there's no use in using another QThread and move your subclass to it;
    • even assuming that an object is moved to another thread, the slot should not be decorated with arguments, since the started signal of a QThread doesn't have any; also consider that slot decorators are rarely required;
    • the thread quit() only stops the event loop of the thread, but if run is overridden no event loop is actually started; if you want to stop a thread with a run implementation, a running flag should be used instead; in any other situation, quitting the application is normally enough to stop everything;

    Note that if you want to interact with the UI, you can only use QThread and custom signals, and basic python threading doesn't provide a suitable mechanism.

    class Threaded(QThread):
        result = pyqtSignal(int)
        def __init__(self, parent=None, **kwargs):
            super().__init__(parent, **kwargs)
    
        def run(self):
            self.keepGoing = True
            while self.keepGoing:
                print("test")
                self.msleep(1)
    
        def stop(self):
            self.keepGoing = False
    
    
    class MainWindow(QMainWindow):
        def __init__(self):
            # ...
            self.threaded = Threaded()
            self.threaded.start()
            qApp.aboutToQuit.connect(self.threaded.stop)