PySide threading and http downloading

2019-04-10 04:47发布

问题:

I've had soooo much trouble getting this code to work properly!!!! It runs fine when I debug it step by step, but when running normally it just crashes. Initially I was using a QThread to update the ImagePreview pixmap, but after a whole day of crashes and pain, I changed course. Now it works, in the above scenario when using the debugger, but otherwise I'm stumped. Please help me! What's wrong with this code? Is there another approach I can use? I'm trying to constantly update the image preview with an image downloaded from a url.

import sys

import io
import urllib2

from PySide import QtCore, QtGui, QtNetwork
import time

class QDownloadBuffer(QtCore.QBuffer):
    downloadFinished = QtCore.Signal()
    def __init__(self):
        super(QDownloadBuffer, self).__init__()
        self.open(QtCore.QBuffer.ReadWrite)
        self.url = QtCore.QUrl("http://www.google.com.au/images/srpr/logo3w.png")
        self.manager = QtNetwork.QNetworkAccessManager()
        self.request = QtNetwork.QNetworkRequest(self.url)
        self.manager.finished.connect(self.onFinished)

    def startDownload(self):
        print("Starting Download --")
        self.reply = self.manager.get(self.request)

        self.reply.error[QtNetwork.QNetworkReply.NetworkError].connect(self.onError)

    def onFinished(self):
        print("Download Finished -- ")
        print(self.write(self.reply.readAll()))
        self.reply.close()
        self.downloadFinished.emit()

    def onError(self):
        print("oh no there is an error -- ")
        print(self.reply.error())

class ImagePreview(QtGui.QWidget):
    def __init__(self, parent=None):
        super(ImagePreview, self).__init__(parent)
        self.setMinimumSize(50, 50)
        self.text = None
        self.pixmap = None
        self.dl_n = 0


    def paintEvent(self, paintEvent):
        painter = QtGui.QPainter(self)

        if(self.pixmap):
            painter.drawPixmap(0, 0, self.pixmap)

        if(self.text):
            painter.setPen(QtCore.Qt.blue)
            painter.setFont(QtGui.QFont("Arial", 30))
            painter.drawText(self.rect(), QtCore.Qt.AlignCenter, self.text)

    def startDownload(self):
        self.setText(str(self.dl_n))
        self.dl_n += 1
        print("Starting Download {0}".format(self.dl_n))

        self.db = QDownloadBuffer()
        self.connect(self.db, QtCore.SIGNAL("downloadFinished()"), self, QtCore.SLOT("ondownloadFinished()"))
        self.db.startDownload()

    def ondownloadFinished(self):
        self.paintImage()
        print("download finished?")
        self.db.close()
        self.startDownload()

    def paintImage(self):
        print("Painting")
        pixmap = QtGui.QPixmap()
        pixmap.loadFromData(self.db.data())
        self.setPixmap(pixmap)

    def setPixmap(self, pixmap):
        self.pixmap = pixmap
        self.setMinimumSize(pixmap.width(), pixmap.height())
        self.update()

    def setText(self, text):
        self.text = text
        self.update()


class MainWindow(QtGui.QWidget):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.imagepreview = ImagePreview()
        self.button = QtGui.QPushButton("Start")
        self.button.clicked.connect(self.imagepreview.startDownload)
        layout = QtGui.QVBoxLayout()
        layout.addWidget(self.button)
        layout.addWidget(self.imagepreview)
        self.setLayout(layout)



if __name__ == "__main__":
    import sys

    try:
        app = QtGui.QApplication(sys.argv)
    except RuntimeError:
        pass

    mainwindow = MainWindow()
    mainwindow.show()

    sys.exit(app.exec_())

回答1:

I think the problem is that you are calling self.startDownload() from slot (signal handler). So you are not returning control to Qt main loop (or something like this). Proper way is to call it as deferred event, e.g. by calling it through QTimer.singleShot:

def ondownloadFinished(self):
    self.paintImage()
    print("download finished?")
    self.db.close()
    QtCore.QTimer.singleShot(0, self.startDownload)

Note that singleShot with msec set to 0:

QtCore.QTimer.singleShot(0, self.startDownload)

is the same as:

QtCore.QMetaObject.invokeMethod(self, 'startDownload',  QtCore.Qt.QueuedConnection)

(source, related question)