1

Short version:

Sending signals to GUI in a sudden manner causes data they carry to be inserted all over the place in the textbox they're meant to go in.

Long version:

I have a PyQt GUI which is connected to a Python script. The script is run in a separate thread and it writes to stdout and stderr. Script can execute independently but I created a GUI to provide a more user-friendly interface.

In order to capture stdout/stderr and write their output in the GUI, I've overwritten sys.stdout and sys.stderr in the thread with an object that has a write method.

sys.stdout = StreamWrapper(sys.stdout, self, self.mutex)
sys.stderr = StreamWrapper(sys.stderr, self, self.mutex)

StreamWrapper:

class StreamWrapper:
    def __init__(self, stream, thread, mutex):
        self.stream = stream # reference to original stream (stdout or stderr)
        self.thread = thread # thread in which this object is initialised.
        self.mutex  = mutex  # mutex ensuring write is only called once at a time

    def write(self, msg):
        with QtCore.QMutexLocker(self.mutex):
            self.thread.emit(QtCore.SIGNAL('update_console(QString)'), msg)
            self.stream.write(msg)
            time.sleep(0.5)

This write method emits a signal and a message to the GUI. The GUI (in the main thread) picks it up and updates a plain text box respectively (called consoleOutput):

self.connect(self.thread, QtCore.SIGNAL('update_console(QString)'), self.ui.consoleOutput.insertPlainText)

I noticed that when there's a sudden burst of activity from stdout, the output will be written in a jumbled up manner in the GUI (literally in any order and displacing but not overwriting previous output). I also write the same output to the console, and this is in perfect order.

To fix it I tried introducing a mutex but to no avail. I then introduced a delay (by way of calling time.sleep(0.5)) and this fixed the problem, albeit in a kludgy manner.

Having looked at signal emitting and order it looks like signals are processed in the order that they are emitted (I'm not overriding any standard behaviour) so order might not be the issue.

Any ideas?

EDIT:

I've created a compilable example consisting of three files that need to be in the same directory:

The compilable example prints random data to screen along with a counter to track the order of the prints. This screenshot shows that some of the data is out of sync with the rest.

Nobilis
  • 7,310
  • 1
  • 33
  • 67
  • 1
    Could you post a minimilistic working example? – three_pineapples May 28 '14 at 21:43
  • @three_pineapples Hi there and many thanks for your interest. I've added links to a compilable working example and a screenshot reproducing the issue in the post. – Nobilis May 29 '14 at 09:21
  • I can;t be 100% sure because I don't have time to run your code right now, but I think the slots are probably being executed in the thread, rather than the GUI thread. Otherwise, the Qt Event loop would be serialising them. Have a look at this question I answered in the past which basically does what you want. http://stackoverflow.com/questions/21071448/redirecting-stdout-and-stderr-to-a-pyqt4-qtextedit-from-a-secondary-thread – three_pineapples May 29 '14 at 11:53
  • @three_pineapples The solution turned out to be a lot simpler than I'd thought. I've posted an answer detailing it. Check it out if you're curious. – Nobilis May 29 '14 at 12:54

1 Answers1

4

Turns out the solution is far simpler and more trivial than I had thought.

Basically, the problem was that I had to move manually the cursor at the end before and after inserting the text.

The solution I followed is detailed here:

To implement it in my case, I first updated the slot receiving the signal by wrapping the insert call in a method (updateConsole):

self.connect(self.fpga_thread, QtCore.SIGNAL('update_console(QString)'), self.updateConsole)

Which looks like this:

def updateConsole(self, msg):
    self.ui.consoleOutput.moveCursor(QtGui.QTextCursor.End)
    self.ui.consoleOutput.insertPlainText(msg)
    self.ui.consoleOutput.moveCursor(QtGui.QTextCursor.End)

And now it works, it seems that the problem is to do with scrolling. If I scroll the text box as it's being updated (without moving the cursor manually) it puts the text at the bottom of the currently displayed text (rather than at the bottom of the text box).

I didn't have to introduce delay after all and removed the sleep but I kept the mutex just in case.

Nobilis
  • 7,310
  • 1
  • 33
  • 67