【问题标题】:What is the proper way of opening a child dialog in a second thread in PyQt when it is previously onknown at what point the dialog will be opened?当以前不知道何时打开对话框时,在 PyQt 的第二个线程中打开子对话框的正确方法是什么?
【发布时间】:2020-06-09 06:02:04
【问题描述】:

我有一个应用程序,我在第二个线程中运行一些进程。在此过程中的某些时间点,如果满足某个条件,则会打开另一个对话框窗口,该窗口会暂停该过程,直到您确认某些内容。这会导致以下错误消息:

QObject: Cannot create children for a parent that is in a different thread.
(Parent is QApplication(0x1f9c82383d0), parent's thread is QThread(0x1f9c7ade2a0), current thread is QThread(0x1f9c8358800)

有趣的是,如果您在进程运行时也将光标移到 MainWindow 上,并且在弹出新对话框之前,它还会多次生成此错误消息:

QBasicTimer::stop: Failed. Possibly trying to stop from a different thread

很奇怪。因为它只有在您将光标移到 MainWindow 上时才会发生。

现在,在我的应用程序中,我实际上为使用PyQt5.uic.loadUi 弹出的新对话框加载了一个界面,这并没有造成任何问题。但是,当我为这篇文章创建示例时,又出现了另一个问题,因为我在初始化期间设置了新对话框的布局:

QObject::setParent: Cannot set parent, new parent is in a different thread

这会导致应用程序崩溃:

Process finished with exit code -1073741819 (0xC0000005)

对于我猜想的线程,我显然在这里做错了,但我不知道是什么。令我特别困惑的是,在初始化期间我无法设置新对话框的布局,而使用loadUi 完全没问题。这是我的示例代码:

import sys
import time
import numpy as np

from PyQt5.QtCore import QObject, pyqtSignal, QThread
from PyQt5.QtWidgets import (
    QDialog, QApplication, QPushButton, QGridLayout, QProgressBar, QLabel
)


class SpecialDialog(QDialog):
    def __init__(self):
        super().__init__()
        btn = QPushButton('pass variable')
        btn.clicked.connect(self.accept)
        layout = QGridLayout()
        layout.addWidget(btn)
        # self.setLayout(layout)
        self.variable = np.random.randint(0, 100)


class Handler(QObject):
    progress = pyqtSignal(int)
    finished = pyqtSignal(int)

    def __init__(self):
        super().__init__()
        self._isRunning = True
        self._success = False

    def run(self):
        result = None
        i = 0
        while i < 100 and self._isRunning:
            if i == np.random.randint(0, 100):
                dialog = SpecialDialog()
                dialog.exec_()
                result = dialog.variable
            time.sleep(0.01)
            i += 1
            self.progress.emit(i)

        if i == 100:
            self._success = True
            self.finished.emit(result)

    def stop(self):
        self._isRunning = False


class MainWindow(QDialog):
    def __init__(self):
        super().__init__()
        btn = QPushButton('test')
        btn.clicked.connect(self.run_test)
        self.pbar = QProgressBar()
        self.resultLabel = QLabel('Result:')
        layout = QGridLayout(self)
        layout.addWidget(btn)
        layout.addWidget(self.pbar)
        layout.addWidget(self.resultLabel)
        self.setLayout(layout)

        self.handler = None
        self.handler_thread = QThread()
        self.result = None

    def run_test(self):
        self.handler = Handler()
        self.handler.moveToThread(self.handler_thread)
        self.handler.progress.connect(self.progress)
        self.handler.finished.connect(self.finisher)
        self.handler_thread.started.connect(self.handler.run)
        self.handler_thread.start()

    def progress(self, val):
        self.pbar.setValue(val)

    def finisher(self, result):
        self.result = result
        self.resultLabel.setText(f'Result: {result}')
        self.pbar.setValue(0)
        self.handler.stop()
        self.handler.progress.disconnect(self.progress)
        self.handler.finished.disconnect(self.finisher)
        self.handler_thread.started.disconnect(self.handler.run)
        self.handler_thread.terminate()
        self.handler = None


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

编辑

忘了说我已经找到this post了,这可能和我的问题有关,不过,我不理解置顶答案中的解决方案的推理,更重要的是,我不说什么我相信是 C++。

【问题讨论】:

    标签: python pyqt pyqt5 qthread


    【解决方案1】:

    如果我们在相等发生之前分析情况,除非初始进度不同,否则在相等发生之后是相同的情况,这表明当相等发生时,必须向 GUI 发送信号以询问信息,并且获得该信息后,启动相同的任务,但初始进度等于发出信号之前的进度。

    from functools import partial
    import sys
    import time
    
    import numpy as np
    
    from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, QThread, QTimer
    from PyQt5.QtWidgets import (
        QDialog,
        QApplication,
        QPushButton,
        QGridLayout,
        QProgressBar,
        QLabel,
    )
    
    
    class SpecialDialog(QDialog):
        def __init__(self):
            super().__init__()
            btn = QPushButton("pass variable")
            btn.clicked.connect(self.accept)
            layout = QGridLayout()
            layout.addWidget(btn)
            # self.setLayout(layout)
            self.variable = np.random.randint(0, 100)
    
    
    class Handler(QObject):
        progress = pyqtSignal(int)
        finished = pyqtSignal()
        resultChanged = pyqtSignal(int)
        requestSignal = pyqtSignal()
    
        def __init__(self):
            super().__init__()
            self._isRunning = True
            self._success = False
            self.iter = 0
            self.result = None
    
        @pyqtSlot()
        def start(self):
            self._isRunning = True
            self._success = False
            self.iter = 0
            self.result = None
            self.task()
    
        @pyqtSlot()
        @pyqtSlot(int)
        def task(self, value=None):
            if value is not None:
                self.result = value
            while self.iter < 100 and self._isRunning:
                if self.iter == np.random.randint(0, 100):
                    self.requestSignal.emit()
                    return
                time.sleep(0.01)
                self.iter += 1
                self.progress.emit(self.iter)
            if self.iter == 100:
                self._success = True
                if self.result:
                    self.resultChanged.emit(self.result)
                self.finished.emit()
    
        def stop(self):
            self._isRunning = False
    
    
    class MainWindow(QDialog):
        def __init__(self):
            super().__init__()
            btn = QPushButton("test")
            btn.clicked.connect(self.run_test)
            self.pbar = QProgressBar()
            self.resultLabel = QLabel("Result:")
            layout = QGridLayout(self)
            layout.addWidget(btn)
            layout.addWidget(self.pbar)
            layout.addWidget(self.resultLabel)
            self.setLayout(layout)
    
            self.handler = None
            self.handler_thread = QThread()
            self.result = None
    
        def run_test(self):
            self.handler = Handler()
            self.handler.moveToThread(self.handler_thread)
            self.handler.progress.connect(self.progress)
            self.handler.resultChanged.connect(self.on_result_changed)
            self.handler.finished.connect(self.on_finished)
            self.handler.requestSignal.connect(self.onRequestSignal)
            self.handler_thread.started.connect(self.handler.start)
            self.handler_thread.start()
    
        def progress(self, val):
            self.pbar.setValue(val)
    
        def onRequestSignal(self):
            dialog = SpecialDialog()
            dialog.exec_()
            wrapper = partial(self.handler.task, dialog.variable)
            QTimer.singleShot(0, wrapper)
    
        @pyqtSlot(int)
        def on_result_changed(self, result):
            self.result = result
            self.resultLabel.setText(f"Result: {result}")
    
        @pyqtSlot()
        def on_finished(self):
            self.pbar.setValue(0)
            self.handler.stop()
            self.handler_thread.quit()
            self.handler_thread.wait()
    
    
    if __name__ == "__main__":
        app = QApplication(sys.argv)
        GUI = MainWindow()
        GUI.show()
        sys.exit(app.exec_())
    

    【讨论】:

      猜你喜欢
      • 2020-06-09
      • 1970-01-01
      • 1970-01-01
      • 2021-05-30
      • 1970-01-01
      • 1970-01-01
      • 2019-01-27
      • 2013-05-26
      相关资源
      最近更新 更多