【问题标题】:pyqt emit signal from threading threadpyqt 从线程线程发出信号
【发布时间】:2018-01-15 07:44:12
【问题描述】:

我正在尝试从多个线程更新 pyqt QProgressBar,据我所知,最好的方法是将信号发送回主 GUI 线程(我尝试将 QProgressBar 对象传递给工作线程,尽管它似乎确实有效,我在解释器中收到了大量警告)。在下面的代码中,我设置了一个 progressSignal 信号并将其连接到一个线程,该线程(目前)只打印发出的任何内容。然后我从每个线程发出总百分比。我知道这在线程之外工作,只需在第 47 行抛出一个随机发射,它确实通过了。然而,第 36 行的发射不会触发任何事情,所以它似乎永远无法通过......

import Queue, threading
from PyQt4 import QtCore
import shutil
import profile

fileQueue = Queue.Queue()

class Communicate(QtCore.QObject):

    progressSignal = QtCore.pyqtSignal(int)

class ThreadedCopy:
    totalFiles = 0
    copyCount = 0
    lock = threading.Lock()

    def __init__(self, inputList, progressBar="Undefined"):
        self.totalFiles = len(inputList)

        self.c = Communicate()
        self.c.progressSignal.connect(self.updateProgressBar)

        print str(self.totalFiles) + " files to copy."
        self.threadWorkerCopy(inputList)


    def CopyWorker(self):
        while True:
            self.c.progressSignal.emit(2000)
            fileName = fileQueue.get()
            shutil.copy(fileName[0], fileName[1])
            fileQueue.task_done()
            with self.lock:
                self.copyCount += 1
                percent = (self.copyCount * 100) / self.totalFiles
                self.c.progressSignal.emit(percent)

    def threadWorkerCopy(self, fileNameList):

        for i in range(16):
            t = threading.Thread(target=self.CopyWorker)
            t.daemon = True
            t.start()
        for fileName in fileNameList:
            fileQueue.put(fileName)
        fileQueue.join()
        self.c.progressSignal.emit(1000)

    def updateProgressBar(self, percent):
        print percent

更新:

这是一个带有 gui 的示例。这个运行但很不稳定,它经常崩溃,UI 做了一些奇怪的事情(进度条没有完成等)

Main.py:

import sys, os
import MultithreadedCopy_5
from PyQt4 import QtCore, QtGui

def grabFiles(path):
    # gets all files (not folders) in a directory
    for file in os.listdir(path):
        if os.path.isfile(os.path.join(path, file)):
            yield os.path.join(path, file)

class MainWin(QtGui.QWidget):

    def __init__(self):
        super(MainWin, self).__init__()
        self.initUI()

    def initUI(self):
        self.progress = QtGui.QProgressBar()

        box = QtGui.QVBoxLayout()
        box.addWidget(self.progress)
        goBtn = QtGui.QPushButton("Start copy")
        box.addWidget(goBtn)

        self.setLayout(box)

        goBtn.clicked.connect(self.startCopy)

    def startCopy(self):
        files = grabFiles("folder/with/files")
        fileList = []
        for file in files:
            fileList.append([file,"folder/to/copy/to"])

        MultithreadedCopy_5.ThreadedCopy(fileList, self.progress)

def main():
    app = QtGui.QApplication(sys.argv)
    ex = MainWin()
    ex.show()
    sys.exit(app.exec_())

if __name__ == "__main__":
    main()

多线程Copy_5.py:

import Queue, threading
from PyQt4 import QtCore
import shutil
import profile

fileQueue = Queue.Queue()

class Communicate(QtCore.QObject):

    progressSignal = QtCore.pyqtSignal(int)

class ThreadedCopy:
    totalFiles = 0
    copyCount = 0
    lock = threading.Lock()

    def __init__(self, inputList, progressBar="Undefined"):
        self.progressBar = progressBar
        self.totalFiles = len(inputList)

        self.c = Communicate()
        self.c.progressSignal.connect(self.updateProgressBar, QtCore.Qt.DirectConnection)

        print str(self.totalFiles) + " files to copy."
        self.threadWorkerCopy(inputList)


    def CopyWorker(self):
        while True:
            fileName = fileQueue.get()
            shutil.copy(fileName[0], fileName[1])
            fileQueue.task_done()
            with self.lock:
                self.copyCount += 1
                percent = (self.copyCount * 100) / self.totalFiles
                self.c.progressSignal.emit(percent)

    def threadWorkerCopy(self, fileNameList):
        for i in range(16):
            t = threading.Thread(target=self.CopyWorker)
            t.daemon = True
            t.start()
        for fileName in fileNameList:
            fileQueue.put(fileName)
        fileQueue.join()

    def updateProgressBar(self, percent):
        self.progressBar.setValue(percent)

#profile.run('ThreadedCopy()')

【问题讨论】:

  • 我开始意识到 python 线程不能将信号发送回主 pyqt 应用程序,因此“正确”的方法是使用 pyqt QThreads - 我不想这样做。每次工作线程完成时,是否有一种简单的方法可以在主线程中发出信号?
  • 请参阅我的答案以获得至少应该解决问题中显示的示例的问题。

标签: python multithreading pyqt pyqt4 qt-signals


【解决方案1】:

您的示例存在两个主要问题。

首先,发出信号的对象是在 main/gui 线程中创建的,因此它发出的任何信号都不会是跨线程的,因此不是线程安全的。对此的明显解决方案是在工作线程的目标函数内部创建信号对象 - 这意味着每个线程必须有一个单独的实例。

其次,目标函数内部的while循环永远不会终止,这意味着每个ThreadedCopy对象将在当前复制操作完成后保持活动状态。由于所有这些对象共享同一个队列,因此如果尝试重复复制操作,行为将变得不可预测。最明显的解决方案是在队列为空时退出 while 循环。

下面是对 MultithreadedCopy_5.py 的重写,它应该可以解决这些问题。但是,正如 cmets 中所述,我仍然强烈建议在这种情况下使用 QThread 而不是 python 线程,因为它可能会提供更强大且更易于维护的解决方案。

import Queue, threading
from PyQt4 import QtCore
import shutil
import profile

fileQueue = Queue.Queue()

class Communicate(QtCore.QObject):
    progressSignal = QtCore.pyqtSignal(int)

class ThreadedCopy:
    totalFiles = 0
    copyCount = 0
    lock = threading.Lock()

    def __init__(self, inputList, progressBar="Undefined"):
        self.progressBar = progressBar
        self.totalFiles = len(inputList)
        print str(self.totalFiles) + " files to copy."
        self.threadWorkerCopy(inputList)

    def CopyWorker(self):
        c = Communicate()
        c.progressSignal.connect(self.updateProgressBar)
        while True:
            try:
                fileName = fileQueue.get(False)
            except Queue.Empty:
                break
            else:
                shutil.copy(fileName[0], fileName[1])
                with self.lock:
                    self.copyCount += 1
                    percent = (self.copyCount * 100) / self.totalFiles
                    c.progressSignal.emit(percent)
                fileQueue.task_done()

    def threadWorkerCopy(self, fileNameList):
        if fileQueue.empty():
            for i in range(16):
                t = threading.Thread(target=self.CopyWorker)
                t.daemon = True
                t.start()
            for fileName in fileNameList:
                fileQueue.put(fileName)
            fileQueue.join()

    def updateProgressBar(self, percent):
        self.progressBar.setValue(percent)

【讨论】:

    【解决方案2】:

    主要问题是发送信号和接收之间的时间延迟,我们可以使用processEvents()来减少这个时间:

    您可以在程序繁忙时偶尔调用此函数 执行长时间操作(例如复制文件)。

    def CopyWorker(self):
        while True:
            fileName = fileQueue.get()
            shutil.copy(fileName[0], fileName[1])
            fileQueue.task_done()
            with self.lock:
                self.copyCount += 1
                print(self.copyCount)
                percent = (self.copyCount * 100) / self.totalFiles
                self.c.progressSignal.emit(percent)
                QtCore.QCoreApplication.processEvents()
    

    【讨论】:

    • 完美!谢谢,这正是我所需要的。
    • "槽在信号线程中执行。"这听起来不太好。 GUI 元素不应该只由 GUI 线程修改吗?
    • @Trilarion 我回答了 OP,目标不是在 GUI 中运行它,所以我提出了这个答案。
    • 但是根据 Qt 文档,这样的事情最好在 GUI 线程中运行,这就是为什么不应该使用这个解决方案,我猜。
    • @Spencer。如果您希望它以线程安全的方式可靠地工作,您应该使用QThread
    猜你喜欢
    • 2017-05-10
    • 2014-09-18
    • 1970-01-01
    • 2018-04-29
    • 2015-08-20
    • 2015-01-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多