3

I have a very short PyQt program (n.b. that is a PythonFiddle link - this seems to crash horribly in Firefox, so code is also posted below) which prints output to a QTextEdit (using code from this SO answer). When I run the code (on Windows), it results in an APPCRASH. Some observations:

  • if I add a time.sleep call (i.e. uncomment out line 53), then the program completes fine
  • if I don’t redirect the output to the QEdit (i.e. comment out line 34) then it works regardless of whether the time.sleep call is commented out or not

I assume that this implies that the code redirecting the stdout is broken somehow - but I'm struggling to understand what's wrong with it to result in this behaviour - any pointers gratefully received!


Full error message

Problem signature:
Problem Event Name: APPCRASH
Application Name: pythonw.exe
Application Version: 0.0.0.0
Application Timestamp: 5193f3be
Fault Module Name: QtGui4.dll
Fault Module Version: 4.8.5.0
Fault Module Timestamp: 52133a81
Exception Code: c00000fd
Exception Offset: 00000000005cbdb7
OS Version: 6.1.7601.2.1.0.256.48
Locale ID: 2057
Additional Information 1: 5c9c
Additional Information 2: 5c9c27bb85eb40149b414993f172d16f
Additional Information 3: bc7e
Additional Information 4: bc7e721eaea1ec56417325adaec101aa


Pythonfiddle crashes horribly on Firefox (for me at least), so code below too:

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_())
Community
  • 1
  • 1
ChrisW
  • 4,970
  • 7
  • 55
  • 92

1 Answers1

5

Ok, one thing first about the thread safety of your program. Because you are connecting a QObject to stdout, calls to print will interact with this QObject. But you should only interact with a QObject from the thread it was created in. The implementation you have is potentially thread unsafe if you call print from a thread other than the one the QObject resides in.

In your case, you are calling print from a QThread while the EmmittingStream(QObject) resides in the main thread. I suggest you thus change your code to make it threadsafe, like so:

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

Note that now calling print from the MainThread of your application will result in thread-unsafe behaviour. Since you aren't doing that at the moment, you are OK for now, but be warned!

I really suggest you read http://qt-project.org/doc/qt-4.8/threads-qobject.html and get your head around everything it talks about. Understanding that document is the key to avoiding annoying crashes due to threads.

--

Now, The issue with the crash is apparently caused by your call to processEvents(). I haven't worked out why (I imagine it is related to threads somehow...), but I can tell you that you do not need that line! Because you are using signals/slots, once the normalOutputWritten method runs, control returns to the Qt Event Loop anyway, and it will continue to process events as normal. There is thus no need to force Qt to process events!

Hope that helps!

EDIT: For an example of how to make your EmittingStream/print calls thread-safe, see here: Redirecting stdout and stderr to a PyQt4 QTextEdit from a secondary thread

Community
  • 1
  • 1
three_pineapples
  • 11,579
  • 5
  • 38
  • 75
  • Thanks very much for the help - I had assumed it was a thread-safety issue but didn't really know where to start. Your initial advice works, but the program seems to wait until it is finished before it prints the output to the QTextEdit... I'll rewrite it using your other answer (and I'm grateful both to you for spending time on an answer and him for having a similar problem at the same time!) – ChrisW Jan 13 '14 at 11:10
  • @ChrisW Ah, my initial advice may not work because you need to decorate the `normalOutputWritten` method with `@QtCore.pyqtSlot(str)`. It is likely the same issue as the following link, but I missed it because I didn't see `connect` be explicitly called (I assume some magic in `QObject` is connecting the signal and slot automatically based on the keyword argument passed to `EmittingStream`) http://stackoverflow.com/questions/20752154/pyqt-connecting-a-signal-to-a-slot-to-start-a-background-operation – three_pineapples Jan 13 '14 at 23:02
  • Hmm, the bad news is that adding the decorator doesn't change anything - I'm seriously out of my depth but too far along with this project to stop! – ChrisW Jan 14 '14 at 15:36
  • AH Ok, I've looked into it a bit more. At least on my system, it is updating the QTextEdit before the end, but it appears to be doing it in chunks. This is to be expected because you are basically firing off many many events from your thread, to the main thread, but the program is not switching back to the MainThread often enough to process them. I find this surprising! One solution is to synchronise the thread to the main thread, so that it only continues the next iteration once the QTextEdit has been updated. Is this Ok? It would help to know what you want to ultimately do in the thread – three_pineapples Jan 14 '14 at 23:32
  • Hmm, offhand I wouldn't know how to synchronise the slave thread back to the main thread *goes off to Google*. The bigger picture of how my code is organised is that I have a main GUI wrapper (`mainWrapper.py` which inherits a `QMainWindow`), a `QWidget` (in `myWidget.py`), and a module (`myProg.py`) where I set up the slave `QThread`. When a form is completed (to set parameters), and a button pressed, on the `QWidget`, the code (which uses these parameters) in `myWidget` is executed. I'm then trying to show progess with a `QProgressBar` on the `QWidget` – ChrisW Jan 15 '14 at 00:58
  • Actually, you may have now realised where my question about progress bars which [you answered well](http://stackoverflow.com/a/21041601/889604) fits in - it probably seemed a bit abstract - and why I want to be able to synchronise a progress bar loop with a separate loop where actual code is being executed – ChrisW Jan 15 '14 at 01:02
  • So the use of the `QTextEdit` is just a placeholder for a future `QProgressBar`? Also, out of interest, is this for a commercial project or just something you are personally playing with? I have some code I'll be releasing soon (hopefully within a week) which would probably solve all of your synchronisation problems, but it will be licensed under the BSD 2-clause license which might not suit you. Let me know! – three_pineapples Jan 15 '14 at 01:16
  • Sorry - slight addition - I want to redirect the output from the code which does the actual work (i.e. in `myProg.py`) into the `QTextEdit`, so the user will have an idea from the `QProgressBar` how far along the program is, and will also have all the verbose info from the code doing the work. This code isn't for anything commercial :) – ChrisW Jan 15 '14 at 01:23
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/45236/discussion-between-three-pineapples-and-chrisw) – three_pineapples Jan 15 '14 at 01:47