Как правильно открыть дочерний диалог во втором потоке в PyQt, если он ранее был известен, в какой момент диалог будет открыт? - PullRequest
1 голос
/ 25 февраля 2020

У меня есть приложение, в котором я запускаю какой-то процесс во втором потоке. В некоторых точках этого процесса, при соблюдении определенного условия, открывается другое диалоговое окно, в котором процесс останавливается до тех пор, пока вы не подтвердите что-либо. Это вызывает следующее сообщение об ошибке:

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 во время выполнения процесса и до появления нового диалогового окна, оно также выдаст это сообщение об ошибке a пару раз:

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

РЕДАКТИРОВАТЬ

Я забыл упомянуть, что я уже нашел этот пост , который может быть связан с моя проблема, однако, я не понимаю причины решения в верхнем ответе, и что более важно, я не говорю то, что я считаю C ++.

1 Ответ

2 голосов
/ 25 февраля 2020

Если мы проанализируем случай до того, как произойдет равенство, то это тот же самый случай после того, как это равенство произойдет, если только начальный прогресс не отличается, с этим это указывает, что когда происходит равенство, сигнал должен быть отправлен на 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_())
...