【问题标题】:PyQt crashing out as a Windows APPCRASHPyQt 作为 Windows APPCRASH 崩溃
【发布时间】:2014-01-10 18:28:22
【问题描述】:

我有一个very short PyQt program(注意,这是一个 PythonFiddle 链接 - 这似乎在 Firefox 中严重崩溃,因此代码也在下面发布),它将输出打印到 QTextEdit(使用代码 from this SO answer)。当我运行代码(在 Windows 上)时,它会导致 APPCRASH。一些观察:

  • 如果我添加一个time.sleep 调用(即取消注释掉第 53 行),那么程序可以正常完成
  • 如果我不将输出重定向到 QEdit(即注释掉第 34 行),那么无论 time.sleep 调用是否被注释掉,它都会起作用

我认为这意味着重定向 stdout 的代码以某种方式被破坏 - 但我正在努力理解导致这种行为的问题所在 - 任何指针都非常感谢!


完整的错误信息

问题签名:
问题事件名称:APPCRASH
应用程序名称:pythonw.exe
应用程序版本:0.0.0.0
申请时间戳:5193f3be
故障模块名称:QtGui4.dll
故障模块版本:4.8.5.0
故障模块时间戳:52133a81
异常代码:c00000fd
异常偏移量:00000000005cbdb7
操作系统版本:6.1.7601.2.1.0.256.48
区域设置 ID:2057
附加信息 1:5c9c
附加信息 2:5c9c27bb85eb40149b414993f172d16f
附加信息 3:bc7e
附加信息4:bc7e721eaea1ec56417325adaec101aa


Pythonfiddle 在 Firefox 上严重崩溃(至少对我而言),所以下面的代码也是:

import os, sys, time, calendar, math
from PyQt4 import QtCore, QtGui

class EmittingStream(QtCore.QObject): 
  textWritten = QtCore.pyqtSignal(str)

  def write(self, text): self.textWritten.emit(str(text))

class myWrapper(QtGui.QMainWindow):

  def __init__(self):
    super(myWrapper, self).__init__()
    self.toolbar = self.addToolBar("MainMenu")
    self.toolbar.addAction(QtGui.QAction("myProg", self, triggered=self.myProgActions))

  def myProgActions(self): self.setCentralWidget(myWidget())

class myWidget(QtGui.QWidget):

  def __init__(self):
    super(myWidget, self).__init__()

    self.myBtn = QtGui.QPushButton('Run!', self)
    self.myBtn.clicked.connect(self.startTest)
    self.outputViewer = QtGui.QTextEdit()

    self.grid = QtGui.QGridLayout()
    self.grid.addWidget(self.myBtn)
    self.grid.addWidget(self.outputViewer)
    self.setLayout(self.grid)

  def startTest(self):
    self.myLongTask = TaskThread()
    sys.stdout = EmittingStream(textWritten=self.normalOutputWritten)
    self.myLongTask.start()

  def normalOutputWritten(self, text):
    cursor = self.outputViewer.textCursor()
    cursor.movePosition(QtGui.QTextCursor.End)
    cursor.insertText(text)
    self.outputViewer.setTextCursor(cursor)
    self.outputViewer.ensureCursorVisible()
    QtGui.qApp.processEvents()

class TaskThread(QtCore.QThread):
  def __init__(self): super(TaskThread, self).__init__()
  def run(self): myProgClass()

class myProgClass:
  def __init__(self):
    for i in range(0,100):
      print "thread", i+1, " ", math.sqrt(i)
      #time.sleep(0.005)

if __name__ == '__main__':
  app = QtGui.QApplication(sys.argv)
  myApp = myWrapper()
  myApp.show()
  sys.exit(app.exec_())

【问题讨论】:

    标签: python pyqt


    【解决方案1】:

    好的,首先要注意程序的线程安全。因为您将QObject 连接到标准输出,所以对print 的调用将与此QObject 交互。但是您应该只与创建它的线程中的QObject 进行交互。如果您从除QObject 之外的线程调用print,那么您拥有的实现可能是线程unsafe居住在。

    在您的情况下,您从QThread 调用print,而EmmittingStream(QObject) 驻留在主线程中。我建议您因此更改代码以使其成为线程安全的,如下所示:

    self.myLongTask = TaskThread()
    sys.stdout = EmittingStream(textWritten=self.normalOutputWritten)
    sys.stdout.moveToThread(self.myLongTask)
    self.myLongTask.start()
    

    请注意,现在从应用程序的 MainThread 调用 print 将导致线程不安全行为。由于您目前没有这样做,所以您现在还可以,但请注意!

    我真的建议您阅读 http://qt-project.org/doc/qt-4.8/threads-qobject.html 并了解它所谈论的所有内容。了解该文档是避免因线程而导致烦人的崩溃的关键。

    --

    现在,崩溃问题显然是由您致电processEvents() 引起的。我还没有弄清楚为什么(我想它与线程有关......),但我可以告诉你,你不需要那条线!因为您正在使用信号/插槽,所以一旦normalOutputWritten 方法运行,控制无论如何都会返回到Qt 事件循环,它将继续正常处理事件。因此无需强制 Qt 处理事件!

    希望有帮助!

    编辑:有关如何使您的 EmittingStream/print 调用线程安全的示例,请参见此处:Redirecting stdout and stderr to a PyQt4 QTextEdit from a secondary thread

    【讨论】:

    • 非常感谢您的帮助 - 我原以为这是一个线程安全问题,但不知道从哪里开始。您最初的建议有效,但是该程序似乎要等到它完成后再将输出打印到 QTextEdit ...我将使用您的其他答案重写它(我很感谢您花时间回答和他同时遇到类似的问题!)
    • @ChrisW 啊,我最初的建议可能不起作用,因为您需要用@QtCore.pyqtSlot(str) 装饰normalOutputWritten 方法。这可能与以下链接相同,但我错过了它,因为我没有看到connect 被显式调用(我假设QObject 中的一些魔术是根据传递给的关键字参数自动连接信号和插槽EmittingStream) stackoverflow.com/questions/20752154/…
    • 嗯,坏消息是添加装饰器不会改变任何东西 - 我严重超出了我的深度,但这个项目太远了,无法停止!
    • 啊好吧,我已经调查了更多。至少在我的系统上,它会在结束之前更新 QTextEdit,但它似乎是分块进行的。这是意料之中的,因为您基本上是从线程触发了许多事件到主线程,但是程序并没有经常切换回 MainThread 来处理它们。我觉得这很令人惊讶!一种解决方案是将线程同步到主线程,以便它仅在 QTextEdit 更新后才继续下一次迭代。这个可以吗?了解您最终想在线程中做什么会有所帮助
    • 嗯,顺便说一句,我不知道如何将从线程同步回主线程转到 Google。我的代码如何组织的更大图景是我有一个主要的 GUI 包装器(mainWrapper.py,它继承了一个 QMainWindow)、一个 QWidget(在 myWidget.py 中)和一个模块(myProg.py),其中我设置从站QThread。当表单完成(设置参数)并按下QWidget 上的按钮时,myWidget 中的代码(使用这些参数)将被执行。然后,我试图在QWidget 上使用QProgressBar 来展示进步
    猜你喜欢
    • 2023-03-19
    • 2012-01-25
    • 2017-03-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多