Emit signals from Worker threads to Main window and back using @pyqtSlot decorators

I'm currently learning to build GUI's using PyQt5 and here is my problem: I have 2 Threads. The first one is QThread object which generates some data. The other one is QObject function which runs in QThread using .moveToThread method and processes data received from my so-called data generator. On main window I emit signal to do some stuff with value returned from generator separately from main thread in order to avoid freezing GUI. The processing looks like:

  • receive data;
  • append it to list;
  • if list is full enough to make some calculations -> emit the result back to GUI;
  • wait some time (e.g. 3 secs) and start again the reception of data.

The problem is in the last point. I freeze the thread after calculations on list items, but it does not block signal emitted from the GUI, so when time.sleep(3) is up, my list already contains 3 items, but I need to start it from 0. Commented fragments of code are my thoughts on how to do it using @pyqtSlot decorators and QtCore.pyqtSignal() but it doesn`t work.

Let me know if any clarification is needed, maybe I missed something.

Will be glad for any help.

Here is my code:

import sys
from PyQt5.QtWidgets import QWidget, QPushButton, QApplication, QGridLayout, QLabel
from PyQt5.QtCore import QObject, QThread, pyqtSlot
from PyQt5 import QtCore
import random
import time


class DataThread(QThread):
    output = QtCore.pyqtSignal(list)

    def __init__(self):
        super().__init__()
        self._run_flag = True

    def run(self):
        while self._run_flag:
            value = round(random.random(), 4)
            value_x2 = value * 2
            values = [value, value_x2]
            self.output.emit(values)
            # print(value)
            time.sleep(1)

    def stop(self):
        self._run_flag = False
        self.quit()


class CalculationsThread(QObject):
    # accepting signals from main data generator thread
    calculations_result = QtCore.pyqtSignal(float)
    calc_value = QtCore.pyqtSignal(float)
    # emitting signal from this thread
    kill = QtCore.pyqtSignal()
    deny_access = QtCore.pyqtSignal(bool)

    def __init__(self, parent=None):
        super(CalculationsThread, self).__init__(parent)
        self.list_of_heights = []

    @pyqtSlot(float)
    def work(self, value):
        # self.deny_access.emit(True)
        self.list_of_heights.append(value)
        self.calculations_result.emit(len(self.list_of_heights))
        if len(self.list_of_heights) == 10:
            result = sum(self.list_of_heights) / len(self.list_of_heights)
            self.calculations_result.emit(result)
            self.list_of_heights = []
            # self.deny_access.emit(False)
            time.sleep(3)


class WinForm(QWidget):
    variable_value = QtCore.pyqtSignal(float)

    def __init__(self, parent=None):
        super(WinForm, self).__init__(parent)
        self.setWindowTitle('QThreads example')
        self.change_flag = 0
        self.calculations_flag = 0
        self.list_from_data_generator_thread = []
        self.thread = None
        self.calculations_worker = None
        self.thread1 = None
        self.label = QLabel('Label')

        self.startBtn = QPushButton('Start')
        self.startBtn.clicked.connect(self.start_data_generator)

        self.endBtn = QPushButton('Stop')
        self.endBtn.clicked.connect(self.stop_data_generator)

        self.output1_btn = QPushButton("Value 1")
        self.output1_btn.clicked.connect(self.show_value_1)
        self.output2_btn = QPushButton("Value 2")
        self.output2_btn.clicked.connect(self.show_value_2)

        self.calculations_label = QLabel("Calculations result: ")
        self.calculations_button = QPushButton("Start calculations")
        self.calculations_button.clicked.connect(self.start_calculations)

        layout = QGridLayout()

        layout.addWidget(self.label, 0, 0)
        layout.addWidget(self.startBtn, 1, 0)
        layout.addWidget(self.endBtn, 1, 1)
        layout.addWidget(self.output1_btn, 2, 0)
        layout.addWidget(self.output2_btn, 2, 1)
        layout.addWidget(self.calculations_label, 3, 0)
        layout.addWidget(self.calculations_button, 3, 1)

        self.setLayout(layout)

    @pyqtSlot(list)
    # @pyqtSLot(bool)
    def set_label_text(self, value):

        # if access_flag:
        self.variable_value.emit(value[1])
        # if not access flag:
        # pass
        if self.change_flag == 0:
            self.label.setText(f"1: {value[0]}")
        if self.change_flag == 1:
            self.label.setText(f"2: {value[1]}")

    def start_data_generator(self):
        self.startBtn.setEnabled(False)
        self.endBtn.setEnabled(True)
        self.thread = DataThread()
        self.thread.output.connect(self.set_label_text)
        self.thread.start()
        self.endBtn.clicked.connect(self.thread.stop)

    def stop_data_generator(self):
        self.thread.output.disconnect(self.set_label_text)
        self.startBtn.setEnabled(True)
        self.endBtn.setEnabled(False)

    @pyqtSlot(float)
    def output_calculations(self, value):
        self.calculations_label.setText(f"Calculations result: {value}")

    def start_calculations(self):
        self.calculations_worker = CalculationsThread()
        self.thread1 = QThread()
        self.calculations_worker.moveToThread(self.thread1)

        self.variable_value.connect(self.calculations_worker.work)
        self.calculations_worker.calculations_result.connect(self.output_calculations)
        self.calculations_worker.kill.connect(self.thread1.quit)

        self.thread1.start()

        self.calculations_button.clicked.disconnect(self.start_calculations)
        self.calculations_button.setText("Stop calculations")
        self.calculations_button.clicked.connect(self.thread1.quit)
        self.calculations_button.clicked.connect(self.stop_calculations)

    def stop_calculations(self):
        self.calculations_worker.calculations_result.disconnect(self.output_calculations)
        self.calculations_button.clicked.disconnect(self.stop_calculations)
        self.calculations_button.clicked.disconnect(self.thread1.quit)
        self.calculations_button.setText("Start calculations")
        self.calculations_button.clicked.connect(self.start_calculations)

    def show_value_1(self):
        self.change_flag = 0

    def show_value_2(self):
        self.change_flag = 1


if __name__ == '__main__':
    app = QApplication(sys.argv)
    form = WinForm()
    form.show()
    sys.exit(app.exec_())

How many English words
do you know?
Test your English vocabulary size, and measure
how many words do you know
Online Test
Powered by Examplum