PYQT6:调用Worker对象方法使程序暂时不响应

发布于 2025-01-21 03:46:47 字数 6650 浏览 2 评论 0原文

我正在尝试使用 Python 和 PyQt6 复制这个。我已经成功地创建了它的工作原型。
问题:
SequenceMemory.request_round 是一个 PyQt6.QtCore.pyqtSignal,当用户要显示新一轮的游戏时(无论是在游戏开始时还是在游戏开始时)都会发出该信号。在上一轮之后。但是,由于某种原因,当发出 request_round 时,GUI 将在大约半秒内无响应。
我已将问题定位到 SequenceMemory 类的 new_round() 方法中的 self.request_round.emit() 行(注释掉该行后,GUI 不会无响应),但我似乎无法弄清楚为什么会发生这种情况。
我认为 CreateSequenceWorker 类中的 sleep() 函数可能是原因,但更改传递给 sleep() 的整数并没有改变如何GUI 长时间没有响应,所以我认为这不是原因。

import sys
import queue
import random
import itertools
import functools
from time import perf_counter


from PyQt6 import QtWidgets as qtw
from PyQt6 import QtGui as qtg
from PyQt6 import QtCore as qtc


class SequenceMemory(qtw.QMainWindow):
    
    SQUARE_PER_ROW = 3

    request_round = qtc.pyqtSignal()
    kill_thread = qtc.pyqtSignal()

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        
        self.sequence_history = []
        self.sequence_queue = queue.Queue()
        
        self.main_menu()

    def main_menu(self):
        start_button = qtw.QPushButton("Start")
        start_button.released.connect(self.start)
        self.main_widget = self.takeCentralWidget()
        self.setCentralWidget(start_button)
        self.show()
        
    def start(self):
        self.setup_GUI()
        self.create_board()
        self.setup_thread()
        self.start_button = self.takeCentralWidget()
        self.setCentralWidget(self.main_widget)
        self.new_round()
  
    def setup_GUI(self):
        self.main_widget = qtw.QWidget()
        self.main_widget.setStyleSheet("background-color: white;")
        self.main_widget.setLayout(qtw.QGridLayout())
        self.main_widget.layout().setSpacing(0)
        self.main_widget.layout().setContentsMargins(5, 5, 5, 10)

    def setup_thread(self):
        self.create_sequence_worker = CreateSequenceWorker()
        self.create_sequence_worker_thread = qtc.QThread()
        self.create_sequence_worker.moveToThread(
            self.create_sequence_worker_thread
        )
        self.create_sequence_worker_thread.start()
        
        self.create_sequence_worker.show_block.connect(self.update_board)
        self.create_sequence_worker.hide_block.connect(self.update_board)

        self.request_round.connect(self.create_sequence_worker.create_sequence)


    def create_board(self):
        self.buttons = [
            tuple(
                qtw.QPushButton()
                for _ in range(self.SQUARE_PER_ROW)
            )
            for _ in range(self.SQUARE_PER_ROW)
        ]

        for row, column in itertools.product(
            range(self.SQUARE_PER_ROW), repeat=2
        ):
            button = self.buttons[row][column]
            button.setStyleSheet(
                """
                background-color: rgb(186, 196, 255);
                border: 5px solid white;
                border-radius: 15px;
                """
                )
            button.setMinimumHeight(150)
            self.main_widget.layout().addWidget(
                button, row, column, 1, 1
            )

    def update_board(self, mode: int, row: int, column: int):
        if mode:
            button = self.buttons[row][column]
            button.setStyleSheet(
                """
                background-color: rgb(186, 255, 196);
                border: 5px solid white;
                border-radius: 15px;
                """
                )
        else:
            button = self.buttons[row][column]
            button.setStyleSheet(
                """
                background-color: rgb(186, 196, 255);
                border: 5px solid white;
                border-radius: 15px;
                """
                )
        
    def new_round(self):
        self.request_round.emit()

    def closeEvent(self, a0: qtg.QCloseEvent):
        self.kill_thread.emit()
        self.create_sequence_worker_thread.quit()
        self.main_menu()


class CreateSequenceWorker(qtc.QObject):
    queue_ready = qtc.pyqtSignal(queue.Queue)
    show_block = qtc.pyqtSignal(int, int, int)
    hide_block = qtc.pyqtSignal(int, int, int)
    
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.kill = False
        self.sequence_history = []
        self.sequence_queue = queue.Queue()
    
    def sleep(self, duration: float):
        start_time = perf_counter()
        while perf_counter() - start_time < duration:
            if self.kill:
                return False
        return True

    def create_sequence(self):
        self.sleep(0.5)
        for row, column in self.sequence_history:
            self.sequence_queue.put((row, column))
            
            self.show_block.emit(1, row, column)
            if self.sleep(0.5):
                self.hide_block.emit(0, row, column)
            else:
                return

        i = 0
        while i < len(self.sequence_history) - self.sequence_queue.qsize() + 1:
            row, column = random.randrange(3), random.randrange(3)
            if self.sequence_history and (row, column) == self.sequence_history[-1]:
                continue

            self.sequence_history.append((row, column))
            self.sequence_queue.put((row, column))
            i+=1
            
            self.show_block.emit(1, row, column)
            if self.sleep(0.5):
                self.hide_block.emit(0, row, column)
            else:
                return

        self.queue_ready.emit(self.sequence_queue)


    def kill_thread_event(self):
        self.kill = True


class MainWindow(qtw.QWidget):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        
        self.setMinimumHeight(450)
        self.setMinimumWidth(450)
        self.setWindowFlags(qtc.Qt.WindowType.FramelessWindowHint)

        self.setLayout(qtw.QVBoxLayout())
        self.layout().setContentsMargins(0, 0, 0, 0)
        self.widget = SequenceMemory()
        self.layout().addWidget(self.widget)

        self.show()
    
    def closeEvent(self, a0) -> None:
        self.widget.close()
        return super().closeEvent(a0)
        
if __name__ == "__main__":
    app = qtw.QApplication(sys.argv)
    mw = MainWindow()
    sys.exit(app.exec())

以上是我可以得到的程序的最小限度,而不偏离程序的结构太远。完整的程序(在单个文件中)可以在此处找到。

I'm trying to replicate this with Python and PyQt6. I have been successful in creating a working prototype of it.
The problem:
SequenceMemory.request_round is a PyQt6.QtCore.pyqtSignal that is emitted when the user is to be displayed a new round for the game, be it at the start of the game or after a previous round. However, for some reason, when request_round is emitted, the GUI goes unresponsive for about half a second.
I've pin-pointed the problem to the self.request_round.emit() line in the new_round() method of the SequenceMemory class (after commenting out that line the GUI does not go unresponsive), but I can't seem to figure out why this occurs.
I thought the sleep() function in the CreateSequenceWorker class might be the cause but changing the integer passed to sleep() didn't change how long the GUI went unresponsive so I don't think that's the cause.

import sys
import queue
import random
import itertools
import functools
from time import perf_counter


from PyQt6 import QtWidgets as qtw
from PyQt6 import QtGui as qtg
from PyQt6 import QtCore as qtc


class SequenceMemory(qtw.QMainWindow):
    
    SQUARE_PER_ROW = 3

    request_round = qtc.pyqtSignal()
    kill_thread = qtc.pyqtSignal()

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        
        self.sequence_history = []
        self.sequence_queue = queue.Queue()
        
        self.main_menu()

    def main_menu(self):
        start_button = qtw.QPushButton("Start")
        start_button.released.connect(self.start)
        self.main_widget = self.takeCentralWidget()
        self.setCentralWidget(start_button)
        self.show()
        
    def start(self):
        self.setup_GUI()
        self.create_board()
        self.setup_thread()
        self.start_button = self.takeCentralWidget()
        self.setCentralWidget(self.main_widget)
        self.new_round()
  
    def setup_GUI(self):
        self.main_widget = qtw.QWidget()
        self.main_widget.setStyleSheet("background-color: white;")
        self.main_widget.setLayout(qtw.QGridLayout())
        self.main_widget.layout().setSpacing(0)
        self.main_widget.layout().setContentsMargins(5, 5, 5, 10)

    def setup_thread(self):
        self.create_sequence_worker = CreateSequenceWorker()
        self.create_sequence_worker_thread = qtc.QThread()
        self.create_sequence_worker.moveToThread(
            self.create_sequence_worker_thread
        )
        self.create_sequence_worker_thread.start()
        
        self.create_sequence_worker.show_block.connect(self.update_board)
        self.create_sequence_worker.hide_block.connect(self.update_board)

        self.request_round.connect(self.create_sequence_worker.create_sequence)


    def create_board(self):
        self.buttons = [
            tuple(
                qtw.QPushButton()
                for _ in range(self.SQUARE_PER_ROW)
            )
            for _ in range(self.SQUARE_PER_ROW)
        ]

        for row, column in itertools.product(
            range(self.SQUARE_PER_ROW), repeat=2
        ):
            button = self.buttons[row][column]
            button.setStyleSheet(
                """
                background-color: rgb(186, 196, 255);
                border: 5px solid white;
                border-radius: 15px;
                """
                )
            button.setMinimumHeight(150)
            self.main_widget.layout().addWidget(
                button, row, column, 1, 1
            )

    def update_board(self, mode: int, row: int, column: int):
        if mode:
            button = self.buttons[row][column]
            button.setStyleSheet(
                """
                background-color: rgb(186, 255, 196);
                border: 5px solid white;
                border-radius: 15px;
                """
                )
        else:
            button = self.buttons[row][column]
            button.setStyleSheet(
                """
                background-color: rgb(186, 196, 255);
                border: 5px solid white;
                border-radius: 15px;
                """
                )
        
    def new_round(self):
        self.request_round.emit()

    def closeEvent(self, a0: qtg.QCloseEvent):
        self.kill_thread.emit()
        self.create_sequence_worker_thread.quit()
        self.main_menu()


class CreateSequenceWorker(qtc.QObject):
    queue_ready = qtc.pyqtSignal(queue.Queue)
    show_block = qtc.pyqtSignal(int, int, int)
    hide_block = qtc.pyqtSignal(int, int, int)
    
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.kill = False
        self.sequence_history = []
        self.sequence_queue = queue.Queue()
    
    def sleep(self, duration: float):
        start_time = perf_counter()
        while perf_counter() - start_time < duration:
            if self.kill:
                return False
        return True

    def create_sequence(self):
        self.sleep(0.5)
        for row, column in self.sequence_history:
            self.sequence_queue.put((row, column))
            
            self.show_block.emit(1, row, column)
            if self.sleep(0.5):
                self.hide_block.emit(0, row, column)
            else:
                return

        i = 0
        while i < len(self.sequence_history) - self.sequence_queue.qsize() + 1:
            row, column = random.randrange(3), random.randrange(3)
            if self.sequence_history and (row, column) == self.sequence_history[-1]:
                continue

            self.sequence_history.append((row, column))
            self.sequence_queue.put((row, column))
            i+=1
            
            self.show_block.emit(1, row, column)
            if self.sleep(0.5):
                self.hide_block.emit(0, row, column)
            else:
                return

        self.queue_ready.emit(self.sequence_queue)


    def kill_thread_event(self):
        self.kill = True


class MainWindow(qtw.QWidget):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        
        self.setMinimumHeight(450)
        self.setMinimumWidth(450)
        self.setWindowFlags(qtc.Qt.WindowType.FramelessWindowHint)

        self.setLayout(qtw.QVBoxLayout())
        self.layout().setContentsMargins(0, 0, 0, 0)
        self.widget = SequenceMemory()
        self.layout().addWidget(self.widget)

        self.show()
    
    def closeEvent(self, a0) -> None:
        self.widget.close()
        return super().closeEvent(a0)
        
if __name__ == "__main__":
    app = qtw.QApplication(sys.argv)
    mw = MainWindow()
    sys.exit(app.exec())

Above is as minimal as I could get the program to be, without diverging too far away from the program's structure. The full program (in a single file) can be found here.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文