Как использовать потоки с переменными внутри функции? PyQt5

У меня есть большая функция, которая замораживает мою программу PyQt5, я попытался использовать для нее другой поток (для этого я использую QThread). Проблема в том, что моей функции нужны некоторые переменные для правильной работы. Как заставить это работать? Я показываю, что я сделал.

Исходный код:

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)

что я пробовал:

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

Я создал класс потока внутри класса QMainWindow, потому что мне нужно передать некоторые переменные (self.i1, self.i2, self.i3) из QMainWindow в функцию, которая будет использовать новый поток. Может и плохо, но никак не работает. Всем спасибо.


person DarkWarrior    schedule 12.05.2020    source источник


Ответы (1)


Вот минимальный рабочий пример, который вы можете адаптировать к своему коду. Несколько замечаний:

  • Вы не должны наследовать от QThread. Вместо этого вы должны создать воркер и переместить его в свой поток.
  • В worker вместо попытки return результата, emit сигнал, который содержит результат, и обрабатывает этот сигнал в вашем приложении.
  • Точно так же вместо того, чтобы пытаться вызвать вашего воркера в обычном режиме, сообщите ему через его слоты через QtCore.QMetaObject.invokeMethod. Как только ваш поток будет запущен, вы можете вызывать этот метод сколько угодно раз.

Дополнительные сведения см. В этом ответе.

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_())


Надеюсь это поможет!

person Asocia    schedule 12.05.2020
comment
Большое спасибо за вашу помощь, но здесь, когда я запускаю его, окно продолжает зависать ... до тех пор, пока не закончатся вычисления или сон. Я думаю, что мы используем один и тот же поток (self.thread) вместо другого, но не уверен ... Я очень новичок! Еще раз спасибо. - person DarkWarrior; 12.05.2020
comment
Ой, ты прав! Я должен был сделать что-то не так. Дай мне проверить... - person Asocia; 12.05.2020
comment
Я отредактировал свой ответ. Оказывается, вам нужно вызывать QtCore.QCoreApplication.processEvents() на каждой итерации в ваших вычислениях. Если у вас нет цикла, попробуйте вызвать его как можно чаще. - person Asocia; 12.05.2020
comment
Я забыл отметить вас, и я не уверен, что вы все еще получаете уведомление об этом, но вы идете @DarkWarrior - person Asocia; 12.05.2020
comment
Эй, да, я понял. Я попытался реализовать это, и проблема в том, что если я использую QcoreApplication.processEvents (), он действительно работает, так как окно больше не замерзает, НО функция занимает от 20 до 25% дольше ... да, как вы сказали, мой цикл причина ... я все еще пытаюсь это исправить ...! - person DarkWarrior; 12.05.2020
comment
Вы действительно уверены, что причина QcoreApplication.processEvents()? Можете ли вы измерить время с ним и без него? - person Asocia; 13.05.2020
comment
Да, я импортировал время, чтобы проверить использованное время. либо: 1. используйте QcoreApplication.processEvents () - ›без зависаний, но медленно, либо 2. не используйте его, быстрее, но зависайте! - person DarkWarrior; 13.05.2020
comment
Позвольте нам продолжить это обсуждение в чате. - person Asocia; 13.05.2020