PDA

View Full Version : Qt widget causing "unexpected async reply"



gct
11th March 2008, 03:56
OK so I've been having some unexpected async reply errors in my GUI, and I've pinned most of them down to doing stupid stuff like updating widgets directly from other threads. I thought I'd gotten rid of it all by moving to custom events, but apparently now. Now I've tracked one down to a widget I have, but it looks to me like it's obeying all the rules of threaded Qt programming, but no matter what I do, when I let the threaded loop run, I can cause an async reply by maximizing/minimizing the GUI rapidly. If I take the loop out, no errors. Can anyone see anything obvious I'm doing wrong:

I've trimmed out some unimportant stuff, but this is basically a widget to source a motion jpeg stream and display it on screen.

Update: I should elaborate that the postCustomEvent call causes the event thread to call the update() method on the widget, thus displaying new frames of video.



class QCameraView(QFrame):
def startMainLoop(self):
# We have a valid HW connection
self.hwConnection = True

# Set pixmap
self.curPixmap = None

# Start up reading thread
self.running = 1
thread.start_new_thread(self.mainLoop, ())

################################################## ################
def __init__(self, parent, cameraHost, testMode=0):
# Initialize parent, send WNoAutoErase to solve flickering problems
QFrame.__init__(self, parent, "none", Qt.WNoAutoErase )

# Make frame shape flat
self.setFrameShape(QFrame.MenuBarPanel)
self.setFrameShadow(QFrame.Plain)

# Open up an image for when there's no connection
self.noConnectPM = QPixmap("Images/nocamera.bmp")

# Return if we don't have physical hardware lying around
if (testMode):
self.curPixmap = self.noConnectPM
self.update()
return

# Save camera host
self.cameraHost = cameraHost

# Set current pixmap
self.curPixmap = None

# Try to open HTTP socket, start ping thread if there's an error
try:
self.inputSocket = urlopen("http://%s/mjpg/video.mjpg" % cameraHost)
except:
self.startPingThread()
else:
if (not self.inputSocket):
self.startPingThread()
else:
self.startMainLoop()


################################################## ################
# Simple destructor, closes down main thread and cleans up sockets
def __del__(self):
self.stop()

def stop(self):
print "[ Shutting down camera connection ]"
self.running = 0
time.sleep(.5)
self.inputSocket.close()


################################################## ################
# Called whenever the widget needs to paint itself
# Simple does a bit-blit if there's a valid pixmap available
def paintEvent(self, event):
if(self.curPixmap):
bitBlt(self, 0, 0, self.curPixmap)


################################################## ################
# Responsible for decoding the motion JPEG stream from the camera
# Basically multiple frames of JPEG data with some frame markers injected
# In the event of a read error, this thread is closed and the ping thread is started
def mainLoop(self):
<some arbitrary pre-processing stuff goes here>

data = read(self.inputSocket, contentSize)
if (data == None):
startPingThread()
return

# Create a QImage
videoImage = QImage()
status = videoImage.loadFromData(data, "JPEG")

# Create a pixmap
self.curPixmap = QPixmap(videoImage)

# Update canvas
postCustomEvent(CameraFrameUpdate())

# Sleep
time.sleep(1.0/float(FRAMERATE))

wysota
11th March 2008, 09:44
I'm not sure but I think you are not allowed to create a QPixmap in a worker thread.

gct
11th March 2008, 14:58
Sure enough, getting rid of the pixmap and blitting from the QImage directly solved the problem. Boy it'd be nice if these things were documented. :mad:

wysota
11th March 2008, 15:04
I think they are documented. QPixmap is one of the "GUI" classes (contrary to QImage) and they can only be used in the main thread.

Oh, here is something. Here we go...


Any operations that generate events must not be called by any thread other than the GUI thread

As pixmaps are stored on the X server, their use obviously generates X11 events.