Noris
Noris

Reputation: 1

Unknown error with QThread after emiting finished signal

I am building an application in PyQt5. There is a point in my code with high computational demand that takes a long time, so to prevent the GUI from freezing I am using QThrads.

 I have a process thread to do the calculations, but I use a different thread to give the user feedback (with help of a progress bar), that the program is actually working. I need to do this because the computation happens i a block, so I can not update my progress bar based on the state of the calculation. I create 2 threads and 2 workers based on the same method, yet when I emit the finish signal of the second worker (signaling that the work is done), the program crashes and throws an error without explanation. The same happens sometimes in debug mood too.

An example where the same problem occurs:

main

import sys
from Workers import LoadingWorker, ProcessWorker
from PyQt5.QtCore import QThread
from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QVBoxLayout, QWidget, QProgressBar, QListWidget

class Window(QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.clicksCount = 0
        self.setupUi()

def setupUi(self):
    # Create widgets
    self.setWindowTitle("MRE")
    self.resize(300, 150)
    self.centralWidget = QWidget()
    self.setCentralWidget(self.centralWidget)
    self.progressBar = QProgressBar()
    self.progressBar.setValue(0)
    self.progressBar.setTextVisible(False)
    self.list = QListWidget()
    self.Btn = QPushButton("Do Stuff", self)
    # Set the layout
    layout = QVBoxLayout()
    layout.addWidget(self.progressBar)
    layout.addWidget(self.list)
    layout.addWidget(self.Btn)
    self.centralWidget.setLayout(layout)
    self.data = None
    self.runtime = 0
    # Add callbacks
    self.Btn.clicked.connect(self.btn_licked)

def btn_licked(self):
    # Call functions to start threads
    self.loading()
    self.process()

def process(self):
    # Set up process worker and get it running
    self.ThreadProcess = QThread()
    self.WorkerProceess = ProcessWorker()
    self.WorkerProceess.moveToThread(self.ThreadProcess)
    self.ThreadProcess.started.connect(self.WorkerProceess.run)
    self.WorkerProceess.finished.connect(self.ThreadProcess.quit)
    self.WorkerProceess.finished.connect(self.WorkerProceess.deleteLater)
    self.ThreadProcess.finished.connect(self.ThreadProcess.deleteLater)
    self.WorkerProceess.progress.connect(self.process_progress)
    self.ThreadProcess.start()
    self.ThreadProcess.finished.connect(self.process_end)

def process_end(self):
    self.WorkerLoading.done = True

def process_progress(self, data, runtime):
    self.data = data
    self.runtime = runtime
    # Some operations on data


def loading(self):
    # Set up loading worker and get it running
    self.ThreadLoading = QThread()
    self.WorkerLoading = LoadingWorker()
    self.WorkerLoading.moveToThread(self.ThreadLoading)
    self.ThreadLoading.started.connect(self.WorkerLoading.run)
    self.WorkerLoading.finished.connect(self.ThreadLoading.quit)
    self.WorkerLoading.finished.connect(self.WorkerLoading.deleteLater)
    self.WorkerLoading.finished.connect(self.ThreadLoading.deleteLater)
    self.WorkerLoading.progress.connect(self.loading_progress)
    self.ThreadLoading.start()
    self.ThreadLoading.finished.connect(self.loading_end)

def loading_progress(self, val):
    self.progressBar.setValue(val)

def loading_end(self,):
    print("Stepped into loading ended")
    self.list.addItem("Runtime (s): " + str(self.runtime))
    self.progressBar.setValue(0)


app = QApplication(sys.argv)
win = Window()
win.show()
sys.exit(app.exec())

Worker Classes

from time import sleep
from PyQt5.QtCore import QObject, pyqtSignal
import numpy as np
from time import time


class ProcessWorker(QObject):
    finished = pyqtSignal()
    progress = pyqtSignal(np.ndarray, float)

    def run(self):
        start_time = time()
        data = np.random.random(size=(50, 500000))
        data = np.fft.fft(np.linalg.pinv(data))
        runtime = time() - start_time
        self.progress.emit(data, runtime)
        self.finished.emit()



class LoadingWorker(QObject):
    finished = pyqtSignal()
    progress = pyqtSignal(int)
    done = False

    def run(self):
        cnt=0
        while (not self.done):
            cnt+=1
            if cnt==101:
                cnt=0
                sleep(0.1)
            sleep(0.005)
            self.progress.emit(cnt)
        self.finished.emit()
        print("loading finish sign supposedly emitted")

The error does not happen all the time:

Process finished with exit code -1073740791 (0xC0000409).

I tried to debug it, but I just can't figure out the cause of the problem.

Upvotes: 0

Views: 343

Answers (1)

musicamante
musicamante

Reputation: 48509

You are deleting the thread when the worker has finished, so you get some sort of race condition: when the finished signal of the worker is emitted, the thread is still running, and you're requesting it to quit and delete it; since, at that point, the thread is still running, this creates a problem.

You should delete the thread when the thread has finished.

Change this:

self.WorkerLoading.finished.connect(self.ThreadLoading.deleteLater)

to this:

self.ThreadLoading.finished.connect(self.ThreadLoading.deleteLater)

Note that for simple cases like this, you can just subclass QThread, implement its run() and start() it.
This makes things easier, as you only have a single object to reference to.

Upvotes: 2

Related Questions