Почему QMessageBox.exec() не блокирует мой поток

#python #multithreading #qt #pyqt

Вопрос:

пожалуйста, проверьте приведенный ниже фрагмент из моего приложения:

 requestDialogSignal = QtCore.pyqtSignal()  class MainWindow(QMainWindow):  def __init__(self):  ...  self.requestDialogSignal.connect(self.slotRequestDialog, Qt.QueuedConnection)  def slotRequestDialog():  mbox = QtWidgets.QMessageBox(QtWidgets.QMessageBox.NoIcon, "title", "message")  mbox.setStandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)  result = mbox.exec()   def CreateDialogs(self):  self.requestDialogSignal.emit()  time.sleep(1)  self.requestDialogSignal.emit()  app = QApplication(sys.argv) window = MainWindow() window.show()  t = threading.Thread(target=window.CreateDialogs) t.start()  app.exec()  

По сути, то, чего я пытался достичь, — это 2 QMessageBox -е место, которое появится только после того, как на первое будет дан ответ. Я ожидал , что первый QMessageBox блокирует поток приемника exec() , что из-за Qt.QueuedConnection не должно позволять вызывать слот во второй раз. Если я изменю соединение Qt.BlockingQueuedConnection , оно будет вести себя так, как ожидалось, хотя это блокирует поток отправки, а это не то, что я хочу.

Комментарии:

1. Использование потоков в этом сценарии неправильно, это не имеет смысла. Также этот пример показывает полное непонимание того, как работает exec (). exec() блокирует ваш код, начиная новый цикл событий и ожидая его завершения. Но этот цикл событий по-прежнему обрабатывает все события (!!!), поэтому второй диалог открывается циклом событий, который вы запустили с помощью exec().

2. Я не совсем понимаю, почему использование потоков в этом сценарии неправильно, не могли бы вы уточнить, пожалуйста? Применение, конечно, сложнее. В этом фрагменте я просто хотел показать, как я пытался использовать Qt.QueuedConnection для сериализации отображения диалоговых окон.

3. Почему нити неправильные? Потоки всегда ошибочны, когда вы их используете, даже если они вам не нужны.

4. Код, содержащий ненужные потоки и соединения в очереди, очень скоро становится недоступным, потому что его логика становится непонятной, как только код становится немного больше.

5. Ваш код очень запутанный. В чем причина такого объявления сигнала вне класса? Являются ли следующие функции частью класса или нет? Зачем нужны потоки (которые, кстати, нельзя использовать для объектов пользовательского интерфейса, так как они небезопасны для потоков, а поток пользовательского интерфейса всегда должен быть основным)? Использование функций блокировки также неправильно, если вы хотите излучать сигнал после задержки, используйте QTimer, а не sleep .

Ответ №1:

Попробуйте что-нибудь в этом роде. Я не тестировал его, так как у меня нет PyQt или PySide на моей машине. Там могут быть некоторые небольшие проблемы, но логика ясна и, я надеюсь, понятна.

 requestDialogSignal = QtCore.pyqtSignal()  class MainWindow(QMainWindow):  def __init__(self):  self.state = 0 # a state variable which can be used to define the dialog opening logic  ...   def openDialog(self):  mbox = QtWidgets.QMessageBox(QtWidgets.QMessageBox.NoIcon, "title", "message", self) # use self to give the dialog a parent; not strictly needed but it is a good practice  mbox.setStandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)  mbox.finished.connect(self.onDialogFinished) # or you can connect to accepted or rejected signals to finetune the logic  mbox.open() # open() makes the dialog window-modal but does not block the code   def onDialogFinished(self):  self.state  = 1  if self.state == 1: # makes sure we open te dialog only twice, but you can change the logic as you like with the state variable  self.openDialog()  app = QApplication(sys.argv) window = MainWindow() window.show() window.openDialog()  app.exec()  

Я считаю, что следующее решение также должно сработать, но оно уродливо по трем причинам. 1) Вызов диалогового exec() окна перед приложением exec() . 2) Использовать слишком много exec() , что не является хорошей практикой. exec() следует избегать, если это возможно. 3) Содержит ненужную рекурсию… Ни одно из этих трех возражений я не считаю неправильным, они просто уродливы, и они мне не нравятся.

 requestDialogSignal = QtCore.pyqtSignal()  class MainWindow(QMainWindow):  def __init__(self):  self.state = 0 # a state variable which can be used to define the dialog opening logic  ...   def execDialog(self):  mbox = QtWidgets.QMessageBox(QtWidgets.QMessageBox.NoIcon, "title", "message", self)  mbox.setStandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)  mbox.exec() # blocks the code until closed  self.state  = 1  if self.state == 1: # makes sure we open te dialog only twice, but you can change the logic as you like with the state variable  self.execDialog()  app = QApplication(sys.argv) window = MainWindow() window.show() window.execDialog()  app.exec()  

Что я нахожу странным в вашем коде, так это то, что вы дважды показываете один и тот же диалог. Это действительно то, чего ты хочешь? Я думаю, вы на самом деле хотите показать два разных диалога… и в этом случае код выглядел бы по-другому. Но в любом случае, я показал вам, как избегать потоков и как правильно их использовать (или избегать) exec() . Я бы очень хотел проголосовать за использование первого из моих примеров, а не второго.