Becca codes
Becca codes

Reputation: 542

Multiple QThread keep crashing PySide

I am trying to implement a program with multiple threads. In the __init__ of the main window, the threads are created. The GUI starts up, while the threads get run in the background. The problem is that it keeps crashing. But if I add/uncomment a line with a print statement, the program works fine.

class TestThreadingWin(QtGui.QMainWindow):

    def __init__(self,  parent=rsui.getMayaMainWindow()):
        ''' Constructor '''
        super(TestThreadingWin, self).__init__(parent)

        self.setWindowTitle('Test Threading')

        self.centralWidget = QtGui.QPlainTextEdit()
        self.setCentralWidget(self.centralWidget)

        self.centralWidget.appendPlainText("test")

        numThreads = 7
        self._threads = []

        for i in range(numThreads):
            #print self._threads  # <-- Uncomment this line, and it works?!
            testThread = QtCore.QThread()
            # Keep a reference to the thread object or it will be deleted when
            # it goes out of scope, even if it has not finished processing.
            self._threads.append(testThread)
            worker = TestThreadWorker(i)
            worker.moveToThread(testThread)

            worker.finishedProcessing.connect(self.updateStuff)
            worker.finishedProcessing.connect(testThread.quit)
            testThread.started.connect(worker.doStuff)
            testThread.finished.connect(self.deleteThread)

            testThread.start()

            QtCore.QCoreApplication.processEvents()

        print 'done creating all threads'

    def deleteThread(self):
        """ Destroy the thread object and remove the reference to it from the 
        self._threads list. """
        print 'delete thread'
        threadToDelete = self.sender()
        threadIndex = self._threads.index(threadToDelete)
        del self._threads[threadIndex]
        threadToDelete.deleteLater()

    def updateStuff(self, message):
        self.centralWidget.appendPlainText(message)

class TestThreadWorker(QtCore.QObject):
    finishedProcessing = QtCore.Signal(str)

    def __init__(self, num):
        super(TestThreadWorker, self).__init__()
        self._num = num

    def doStuff(self):
        time.sleep(1)
        choiceList = ['cat', 'bat', 'hat', 'tissue', 'paper', 'qwerty', 'mouse']
        stuff = random.choice(choiceList)
        stuff2 = '{0} {1}'.format(self._num, stuff)
        self.finishedProcessing.emit(stuff2)

def openThreadingWin():
    '''This ensures that only one instance of the UI is open at a time.'''
    global testingThreadingWin
    try:
        testingThreadingWin.close()
        testingThreadingWin.deleteLater()
    except: pass
    testingThreadingWin = TestThreadingWin()
    testingThreadingWin.show()

It is weird that a print statement would make it stop crashing. What am I overlooking?

Upvotes: 0

Views: 1107

Answers (1)

Becca codes
Becca codes

Reputation: 542

I finally got it working by using QThreadPool. It manages the threads for me, so I don't have to worry about trying to access something that was already destroyed. Key differences:

  • The worker class now inherits from both QtCore.QObject and QtCore.QRunnable. (It has to inherit from QObject in order to emit a signal.) The __init__ function of both parent classes must be called, or the program will crash.
  • No more code to set up connections to ensure the thread will be destroyed when done.
  • No more code to keep references to threads or delete those references when the thread is destroyed.

Here is the new code:

class TestThreadPoolWin(QtGui.QMainWindow):
    def __init__(self,  parent=rsui.getMayaMainWindow()):
        ''' Constructor '''
        super(TestThreadPoolWin, self).__init__(parent)

        self.setWindowTitle('Test Threading')

        self.centralWidget = QtGui.QPlainTextEdit()
        self.setCentralWidget(self.centralWidget)

        self.centralWidget.appendPlainText("test")

        numThreads = 7
        threadPool = QtCore.QThreadPool.globalInstance()
        for i in range(numThreads):
            runnable = TestRunnableWorker(i)
            runnable.finishedProcessing.connect(self.updateStuff)
            threadPool.start(runnable)

        print 'done creating all threads'

    def updateStuff(self, message):
        self.centralWidget.appendPlainText(message)

class TestRunnableWorker(QtCore.QObject, QtCore.QRunnable):
    finishedProcessing = QtCore.Signal(str)

    def __init__(self, num, parent=None):
        # Be sure to run the __init__ of both parent classes, or else you will
        # get a crash.
        QtCore.QObject.__init__(self, parent)
        QtCore.QRunnable.__init__(self)

        self._num = num

    def run(self):
        time.sleep(1)
        choiceList = ['cat', 'bat', 'hat', 'tissue', 'paper', 'qwerty', 'mouse']
        stuff = random.choice(choiceList)
        stuff2 = '{0} {1}'.format(self._num, stuff)
        self.finishedProcessing.emit(stuff2)

def openThreadPoolWin():
    '''This ensures that only one instance of the UI is open at a time.'''

    global testingThreadPoolWin
    try:
        testingThreadPoolWin.close()
        testingThreadPoolWin.deleteLater()
    except: pass
    testingThreadPoolWin = TestThreadPoolWin()
    testingThreadPoolWin.show()

Upvotes: 1

Related Questions