在Python和PySide中使用超时的线程上传

时间:2015-05-27 19:17:35

标签: python multithreading pyqt multiprocessing pyside

我正在寻找一种基于线程的设计模式。线程,多处理或队列来上传超时的项目列表。该线程允许GUI保持响应。如果连接挂起,则应触发超时,程序应正常退出。

以下示例有效,但GUI仍然被阻止。如何改进以允许上传列表,手动取消进程,上传进程超时以及非阻塞GUI?

from PySide.QtGui import *
from PySide.QtCore import *
import sys
import time
import threading

class UploadWindow(QDialog):
	def __init__(self, parent=None):
		super(UploadWindow, self).__init__(parent)
		
		self.uploadBtn = QPushButton('Upload')
		mainLayout = QVBoxLayout()
		mainLayout.addWidget(self.uploadBtn)
		self.uploadBtn.clicked.connect(self.do_upload)

		self.progressDialog = QProgressDialog(self)
		self.progressDialog.canceled.connect(self.cancelDownload)
		self.progressDialog.hide()
		
		self.setLayout(mainLayout)
		self.show()
		self.raise_()


	def do_upload(self):
		self.uploadBtn.setEnabled(False)
		self.progressDialog.setMaximum(10)
		self.progressDialog.show()
		self.upload_thread = UploadThread(self)
		self.upload_thread.start()
		self.upload_thread_stopped = False

		#List of items to upload
		for i in range(10):
			self.upload_thread = UploadThread(i)
			self.upload_thread.start()
			self.upload_thread.join(5)
			self.progressDialog.setValue(i)
			if self.upload_thread_stopped:
				break

		self.progressDialog.hide()
		self.uploadBtn.setEnabled(True)


	def cancelDownload(self):
		self.upload_thread_stopped = True


class UploadThread(threading.Thread):
	
	def __init__(self, i):
		super(UploadThread, self).__init__()
		self.i = i
		self.setDaemon(True)
		

	def run(self):
		time.sleep(0.25) #simulate upload time
		print self.i


if __name__ == '__main__':
	app = QApplication(sys.argv)
	w = UploadWindow()
	sys.exit(app.exec_())

3 个答案:

答案 0 :(得分:1)

GUI没有响应,因为您在do_upload中完成所有工作,从不返回主循环。

另外,你调用Thread.join(),阻止一切,直到线程完成(参见https://docs.python.org/2/library/threading.html#threading.Thread.join

您应该使用PySide.QtCore.QThread来利用信号和广告位。 这是一个nice example in C++。我使用PyQt here在Python3.4中实现了它,但你也应该能够将它与PySide一起使用。

您可能还想查看PySide.QtCore.QProcess,以避免使用线程。

答案 1 :(得分:1)

在这里,我将一些代码放在一起,做了我认为你想要的东西。

对于一个真实的项目,请务必跟踪更好的上传内容和/或使用比.terminate()更安全的内容来按需停止线程。

import sys
from PySide import QtGui, QtCore
import time

class MySigObj(QtCore.QObject):
    strSig = QtCore.Signal(str)
    tupSig = QtCore.Signal(tuple)

class UploadThread(QtCore.QThread):

    def __init__(self, parent=None):
        super(UploadThread, self).__init__(parent)
        self.endNow = False
        self.fileName = None
        self.sig = MySigObj()
        self.fileNames = []
        self.uploaded = []

    @QtCore.Slot(str)
    def setFileNames(self, t):
        self.fileNames = list(t)

    def run(self):
        while self.fileNames:
            print(self.fileNames)
            time.sleep(2)
            name = self.fileNames.pop(0)
            s = 'uploaded file: ' + name + '\n'
            print(s)
            self.sig.strSig.emit(s)
            self.uploaded.append(name)
            if len(self.fileNames) == 0:
                self.sig.strSig.emit("files transmitted: %s" % str(self.uploaded))
        else:
            time.sleep(1)   #if the thread started but no list, wait 1 sec every cycle thru
                            #that was this thread should release the Python GIL (Global Interpreter Lock)


class ULoadWin(QtGui.QWidget):

    def __init__(self, parent=None):
        super(ULoadWin, self).__init__(parent)
        self.upThread = UploadThread()
        self.sig = MySigObj()
        self.sig.tupSig.connect(self.upThread.setFileNames)
        self.upThread.sig.strSig.connect(self.txtMsgAppend)
        self.sig.tupSig.connect(self.upThread.setFileNames)
        self.layout = QtGui.QVBoxLayout()
        self.stButton = QtGui.QPushButton("Start")
        self.stButton.clicked.connect(self.uploadItems)
        self.stpButton = QtGui.QPushButton("Stop")
        self.stpButton.clicked.connect(self.killThread)
        self.testButton = QtGui.QPushButton("write txt\n not(?) blocked \nbelow")
        self.testButton.setMinimumHeight(28)
        self.testButton.clicked.connect(self.tstBlking)
        self.lbl = QtGui.QTextEdit()
        self.lbl.setMinimumHeight(325)
        self.lbl.setMinimumWidth(290)

        self.layout.addWidget(self.stButton)
        self.layout.addWidget(self.stpButton)
        self.layout.addWidget(self.testButton)
        self.layout.addWidget(self.lbl)

        self.setLayout(self.layout)

        self.l = ['a', 'list', 'of_files', 'we', 'will_pretend_to_upload', 'st', 'uploading']
        self.upThread.start()

    def tstBlking(self):
        self.lbl.append("txt not(?) blocked")

    def uploadItems(self):
        t = tuple(self.l)
        self.sig.tupSig.emit(t)
        self.upThread.start()

    def killThread(self):
        self.upThread.terminate()
        time.sleep(.01)
        self.upThread = UploadThread()

    @QtCore.Slot(str)
    def txtMsgAppend(self, txt):
        self.lbl.append(txt + "  |  ")


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

答案 2 :(得分:1)

最后,我通过调整here概述的方法解决了这个问题,我发现这是一个优雅的解决方案。我创建的课程如下所示:

class UploadThread(threading.Thread):

    #input_q and result_q are Queue.Queue objects
    def __init__(self, input_q, result_q):
        super(UploadThread, self).__init__()
        self.input_q = input_q
        self.result_q = result_q
        self.stoprequest = threading.Event() #threadsafe flag

    def run(self):
        '''Runs indefinitely until self.join() is called. 
        As soon as items are placed in the input_q, then the thread will process them until the input_q is emptied.
        '''
        while not self.stoprequest.isSet(): #stoprequest can be set from the main gui
            try:
                # Queue.get with timeout to allow checking self.stoprequest
                num = self.input_q.get(True, 0.1) #when the queue is empty it waits 100ms before raising the Queue.Empty error
                print 'In thread, processing', num
                time.sleep(0.5)
                self.result_q.put(True) #Indicate to the main thread that an item was successfully processed.
            except Queue.Empty as e:
                continue

    def join(self, timeout=None):
        self.stoprequest.set()
        super(UploadThread, self).join(timeout)

在主线程中,创建了上传线程,并在input_q中加载了要上传的项目。创建QTimer以通过检查放置到result_q中的内容来定期检查上载的进度。它还会更新进度条。如果在超时内没有进展,则表示上载连接失败。

使用Queue.Queue对象在线程之间进行通信的一个优点是可以创建共享相同输入和结果队列的多个线程。