0

I have a big function which freezes my PyQt5 program, I tried to use a different thread for it (I use QThread for that). Problem is my function needs some variables to work properly. How to make this works? I show what I did.

Original code:

class AnalysisWindow(QtWidgets.QMainWindow):

    def __init__(self, firstWindow):
        super(AnalysisWindow, self).__init__()       
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.ui.pushButton.clicked.connect(self.letsgo)

    def letsgo(self):
        #here some code , not big
        #and at some point i have a heavy one which make it freeze until it's over:
          self.x1, self.x2, self.y1,self.y2, self.z, = self.analyze(self.i1, self.i2, self.i3)

    def analyze(self,i1,i2,i3):
        #big function
        return(x1,x2,y1,y2,z)

what I tried :

from PyQt5.QtCore import Qt, QThread, pyqtSignal


class AnalysisWindow(QtWidgets.QMainWindow):

    class MyThread(QThread):         

        _signal =pyqtSignal()
        def __init__(self):
            super().__init__()

        def run(self,i1,i2,i3):  # here I obviously can't put variables

            #I copied here my analyze function
            return(x1,x2,y1,y2,z)         

            self._signal.emit()

    def __init__(self, firstWindow):
        super(AnalysisWindow, self).__init__()       
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.ui.pushButton.clicked.connect(self.letsgo)

    def letsgo(self):
        self.thread = MyThread()           
        self.thread.start()
        #here I dont see how to send the variables self.i1, self.i2, self.i3 and how to get the result: x1,x2,y1,y2,z

I created the thread class inside the QMainWindow class because i need to pass some variables (self.i1, self.i2, self.i3) from QMainWindow to the function which will use the new thread. Maybe that's bad, but it doesn't work in any way. Thanks everyone.

DarkWarrior
  • 114
  • 1
  • 11

1 Answers1

0

Here is a minimal working example that you can adapt it to your code. Few things to note:

  • You should not inherit from QThread. Instead, you should create a worker and move it into your thread.
  • In worker, instead of trying to return a result, emit the signal that holds the result and process that signal in your application.
  • Similarly, instead of trying to call your worker normally, communicate it through its slots via QtCore.QMetaObject.invokeMethod. Once your thread is started, you can call this method as much as you want.

Refer to this answer for more

import sys
import random
from PyQt5.QtCore import QThread, pyqtSignal, QObject, pyqtSlot, Qt
from PyQt5 import QtWidgets
from PyQt5 import QtCore


class Analyzer(QObject):
    analyze_completed = pyqtSignal(bool)
    analyze_result = pyqtSignal(list, int)

    @pyqtSlot(str, list)
    def analyze(self, foo, analyze_args):
        print(foo, analyze_args)
        self.analyze_completed.emit(False)

        # do your heavy calculations
        for i in range(10000000):
            x = i ** 0.5

        result = sum(analyze_args)
        self.analyze_result.emit(analyze_args, result)
        self.analyze_completed.emit(True)


class AnalysisWindow(QtWidgets.QWidget):

    def __init__(self):
        super().__init__()
        self.label = QtWidgets.QLabel("")
        self.i = 0
        self.label_i = QtWidgets.QLabel("Value of i: {}".format(self.i))
        self.increment_button = QtWidgets.QPushButton("increment i")
        self.pushbutton = QtWidgets.QPushButton("Analyze")
        super(AnalysisWindow, self).__init__()
        self.analyze_args = []
        self.analyzer = Analyzer()
        self.thread = QThread()
        self.analyzer.analyze_result.connect(self.on_analyze_result_ready)
        self.analyzer.analyze_completed.connect(self.on_analyze_completed)
        self.analyzer.moveToThread(self.thread)
        self.thread.start()
        self.init_UI()

    def init_UI(self):
        grid = QtWidgets.QGridLayout()
        grid.addWidget(self.label, 0, 0)
        grid.addWidget(self.pushbutton)
        grid.addWidget(self.label_i)
        grid.addWidget(self.increment_button)
        self.increment_button.clicked.connect(self.increment_i)
        self.pushbutton.clicked.connect(self.start_analyze)
        self.setLayout(grid)
        self.move(300, 150)
        self.setMinimumSize(300, 100)
        self.setWindowTitle('Thread Test')
        self.show()

    def start_analyze(self):
        self.analyze_args.clear()
        self.analyze_args.extend(random.choices(range(100), k=5))
        QtCore.QMetaObject.invokeMethod(self.analyzer, 'analyze', Qt.QueuedConnection,
                                        QtCore.Q_ARG(str, "Hello World!"),
                                        QtCore.Q_ARG(list, self.analyze_args))

    def increment_i(self):
        self.i += 1
        self.label_i.setText("Value of i: {}".format(self.i))

    def on_analyze_result_ready(self, args, result):
        t = "+".join(str(i) for i in args)
        self.label.setText(f"{t} = {result}")

    def on_analyze_completed(self, completed):
        if completed:
            self.label.setStyleSheet('color: blue')
        else:
            self.label.setText(
                "Analyzing... {}".format(", ".join(str(i) for i in self.analyze_args)))
            self.label.setStyleSheet('color: yellow')


app = QtWidgets.QApplication(sys.argv)

widget = AnalysisWindow()

sys.exit(app.exec_())


Hope this helps!

Asocia
  • 5,935
  • 2
  • 21
  • 46
  • Thank you very much for your help, but here when I run it, the window keeps freezing...until the calculations or the sleep is finished. I think we use the same thread (self.thread) instead of another one, but not sure... Im a very beginner! Thanks again. – DarkWarrior May 12 '20 at 17:18
  • Oops, you are right! I should have done something wrong. Let me check... – Asocia May 12 '20 at 17:57
  • I edited my answer. It turns out you need to call `QtCore.QCoreApplication.processEvents()` at every iteration in your calculation. If you don't have any loop try to call it as much as possible. – Asocia May 12 '20 at 18:18
  • I forgot to tag you and I'm not sure if you still get notified about this but there you go @DarkWarrior – Asocia May 12 '20 at 18:43
  • Hey, Yes I got it. I tried to implement this, and the problem is if I use QcoreApplication.processEvents(), it does work, as the window is not freezing anymore, BUT the function take 20 to 25% longer... yes, as you said my loop is the reason... im still trying to fix this...! – DarkWarrior May 12 '20 at 20:40
  • Are you really sure that the reason is `QcoreApplication.processEvents()`? Can you time running time with and without it? – Asocia May 12 '20 at 21:11
  • Yes i did import time to check the time used. its either : 1. use QcoreApplication.processEvents() --> no freezing but slow or 2. dont use it, faster but freezing! – DarkWarrior May 13 '20 at 07:35
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/213789/discussion-between-asocia-and-darkwarrior). – Asocia May 13 '20 at 14:44