【问题标题】:Syncing activity in PyQt QThreadsPyQt QThreads 中的同步活动
【发布时间】:2014-01-27 01:32:08
【问题描述】:

我在玩 PyQt 和 QThreads。似乎如果我使用我在this python fiddle 中输入的代码(请注意,顶部是 QtDesigner 自动生成的代码),其中循环的当前值会在从属线程的循环中打印,并且循环控制进度条,然后循环保持同步,程序运行时所有点的值匹配,进度条准确显示从线程完成的比例。

响应下面的评论,这个程序在其当前状态下实际上做了我想要它做的事情——它只是向终端打印出从线程中的循环值和循环中的值控制进度条的进度。

但是,注释掉第 121 行(即,如果您不打印进度条循环中的当前值),会导致从线程循环仅达到 ~ 时进度条达到 100%(即完成 300 次迭代) 130 次迭代(即进度条完成速度快了大约 100%)。

我是否做了一些天真的愚蠢/错误的事情 - 有没有更好的方法来完成我想做的事情?!

【问题讨论】:

  • 无疑有更好的方法来完成你想做的事情,但我不清楚你想做什么。你能详细说明你的程序试图完成什么吗?例如,您打算在线程中发生什么?同时,我建议阅读有关 python 线程的文档。虽然您不使用 Python 线程,但您可以在 QThread 中使用大量同步工具,例如 threading.Lockthreading.Event
  • 我试图更清楚地解释这个程序的作用——它只是一个测试程序,所以没有任何重点;我只是用它来尝试和学习线程......

标签: python multithreading pyqt


【解决方案1】:

仅仅因为您有两个循环在不同的线程中执行相同次数的迭代,并不意味着它们将花费相同的时间来完成。通常,每次迭代的内容都需要一些时间才能完成,而且由于您的循环正在做不同的事情,它们(通常)会花费不同的时间。

此外,对于 Python 中的线程,全局解释器锁 (GIL) 可阻止线程同时在多核 CPU 上运行。 GIL 负责在线程之间切换,并继续它们的执行,然后再切换到另一个,然后是另一个,等等。使用 QThreads,这变得更加复杂,因为 QThread 中的 Qt 调用可以在没有 GIL 的情况下运行(所以我理解),但一般的 Python 代码仍将使用 GIL 运行。

因为 GIL 负责在任何给定时间处理正在运行的线程,所以我什至见过两个线程,做完全相同的事情,以不同的速度运行。 因此,您的两个线程同时完成完全是巧合!

请注意,由于 GIL,两个 CPU 密集型任务不会因在单独的线程中运行而提高速度。为此,您需要使用多处理。但是,如果您想分出 I/O 绑定任务(例如通过主线程中的 GUI 进行用户界面,另一个线程中的网络通信,也就是通常花费大量时间等待程序外部的某些内容的任务)触发某些东西)然后线程很有用。

所以,希望这有助于解释线程,以及您的程序中发生了什么。

有几种方法可以更好地做到这一点。一种是将循环保留在您的线程中,但删除另一个。然后使用 qt 信号/槽机制调用MainWindow 中的函数,该函数运行曾经存在的循环的一次迭代。但这并不能保证同步,只是您的 QThread 将首先完成(某些事情可能会减慢主线程的速度,从而导致事件堆积,MainWindow 中的函数直到稍后才会运行)。要完成同步,您可以使用threading.Event 对象让QThread 等待MainWindow 中的新函数运行。

示例(未经测试,抱歉,但希望能给出想法!):

import threading
#==========================================
class TaskThread(QtCore.QThread):

    setTime = QtCore.pyqtSignal(int,int)
    iteration = QtCore.pyqtSignal(threading.Event, int)

    def run(self):

        self.setTime.emit(0,300)
        for i in range(300):
            time.sleep(0.05)
            event = threading.Event()
            self.iteration.emit(event, i)
            event.wait()

#==========================================
class MainWindow(QtGui.QMainWindow):

    _uiform = None

    def __init__(self, parent=None):
        QtGui.QMainWindow.__init__(self,parent)
        self._uiform = Ui_MainWindow()
        self._uiform.setupUi(self)
        self._uiform.runButton.clicked.connect(self.startThread)


    def startThread(self):
        self._uiform.progressBar.setRange(0,0)
        self.task = TaskThread()
        self.task.setTime.connect(self.changePB)
        self.task.iteration.connect(self.update_prog_bar)
        self.task.start()

    @QtCore.pyqtSlot(int,int)
    def changePB(self, c, t):
        self.proportionFinished = int(math.floor(100*(float(c)/t)))
        self._uiform.progressBar.setValue(self.proportionFinished)

        self._uiform.progressBar.setRange(0,300)
        self._uiform.progressBar.setValue(0)

    @QtCore.pyqtSlot(threading._Event,int)
    def update_prog_bar(self,event, i)
        self._uiform.progressBar.setValue(i+1)
        print i
        event.set()

注意@QtCore.pyqtSlot() 装饰器的使用是因为here 记录的问题。简而言之,当您使用signal.connect(my_function) 时,您忽略了决定插槽行为的第二个参数(无论是在调用signal.emit() 时立即执行,还是在控制返回事件循环后执行(又名,放置在稍后运行的队列中))。默认情况下,connect 的这个可选参数会尝试自动决定建立哪种类型的连接(请参阅here),这通常会起作用。但是,如果在知道它是线程之间的连接之前就建立了连接,并且“插槽”**没有*使用@pyqtSlot明确定义为插槽,那么pyQT会感到困惑!

有关装饰器的其他信息:考虑装饰器的最简单方法是将函数包装在另一个函数中的简写。装饰器用它自己的函数替换你定义的函数,这个新函数通常在某个时候使用你原来的函数。所以在@pyqtSlot的情况下,信号发射实际上调用了@pyqtSlot生成的pyqt函数,而这个函数最终调用了你写的原始函数。 @pyqtSlot 装饰器采用与插槽参数类型匹配的参数这一事实是 pyqt 装饰器的实现细节,通常不代表装饰器。它只是说明您的插槽期望通过连接信号传递指定类型的数据。

【讨论】:

  • '因此,您的两个线程同时完成完全是巧合!' - 虽然我可以接受这一点,但我完全无法理解为什么他们在我运行程序的数十次中每次都同时完成!
  • 使用这段代码,我最初得到了iteration = QtCore.pyqtSignal(threading.Event, int) TypeError: string or ASCII unicode expected not 'function';更改为iteration = QtCore.pyqtSignal(threading._Event, int) 修复了它:) 运行程序时,我得到了TypeError: changePB() takes exactly 3 arguments (1 given) TypeError: update_prog_bar() takes exactly 3 arguments (1 given) - 我可以解决这个问题的唯一方法是注释掉装饰器 - 我对装饰器的了解还不够,还不知道它们的用途,它们有什么不同,或者您链接到的答案中的解释
  • 啊,我认为(/假设?)我找到了解决方案:装饰器采用python types的参数:@QtCore.pyqtSlot(int,int)@QtCore.pyqtSlot(threading._Event, int) 工作
  • @ChrisW 啊,是的,我很抱歉。你确实正确地解决了它。我自己并不经常使用装饰器,但最近意识到在线程中使用它是明智的!我将更新我的帖子(并添加一些关于装饰器的内容以及关于我链接到的记录问题的更多细节)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-10
  • 1970-01-01
  • 2023-03-08
  • 2011-03-28
  • 1970-01-01
相关资源
最近更新 更多