在PyQt QThreads中同步活动

发布于 2021-01-29 17:28:53

我在玩PyQt和QThreads。看来,如果我使用放在此python小提琴中的代码(请注意,顶部是从QtDesigner自动生成的代码),则该循环的当前值会在从属线程和循环控制进度条,然后循环保持同步,值在程序运行时的所有点都匹配,并且进度条准确显示已完成的从属线程的比例。

作为对以下评论的回应,处于当前状态的该程序实际上可以执行我想要的操作-它只是将输出从线程中的循环值和控制进度的循环中的值打印到终端进度条。

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

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

关注者
0
被浏览
48
1 个回答
  • 面试哥
    面试哥 2021-01-29
    为面试而生,有面试问题,就找面试哥。

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

    此外,借助Python中的线程,全局解释器锁(GIL)阻止了线程在多核CPU上同时运行。GIL负责在线程之间切换并继续执行,然后再切换到另一个,然后再切换到另一个,依此类推。对于QThreads,这变得更加复杂,因为QThread中的Qt调用无需GIL即可运行(因此了解),但一般的python代码仍将与GIL一起运行。

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

    请注意,由于使用了GIL,两个cpu密集型任务在单独的线程中运行不会带来速度上的好处。为此,您需要使用多重处理。但是,如果您要输出受I /
    O约束的任务(例如,通过主线程中的GUI进行用户界面操作,或者通过另一个线程中的网络通信,也就是通常花费大量时间等待程序外部内容的任务),触发某物),则线程化很有用。

    因此,希望这有助于解释线程以及程序中发生的事情。

    有两种方法可以更好地做到这一点。一种是将循环保留在您的线程中,而另一种则删除。然后使用qt signal /
    slot机制调用一个函数,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()装饰器是由于此处记录的问题。简而言之,当您使用时signal.connect(my_function),您将忽略确定插槽行为的第二个参数(是否在signal.emit()调用时立即执行该插槽,或者是否在控件返回事件循环后立即执行该插槽(又称为队列),稍后再运行))。默认情况下,connect的这个可选参数会尝试自动确定通常可以进行哪种连接(请参阅此处)。但是,如果在知道连接是线程之间的连接之前进行了连接,并且使用@pyqtSlot,没有明确地将“
    slot”定义为插槽,则pyQT会感到困惑!

    有关装饰器的其他信息 :想到 装饰器
    的最简单方法是将一个函数包装在另一个函数中的简写形式。装饰器将自己定义的函数替换为自己的函数,并且此新函数通常会在某个时候使用原始函数。因此,在这种@pyqtSlot情况下,信号发射实际上调用了由生成的pyqt函数@pyqtSlot,该函数最终调用了您编写的原始函数。@pyqtSlot装饰器接受与插槽参数类型匹配的参数的事实是pyqt装饰器的实现细节,通常不代表装饰器。仅说明您的插槽期望通过连接的信号传递指定类型的数据。



知识点
面圈网VIP题库

面圈网VIP题库全新上线,海量真题题库资源。 90大类考试,超10万份考试真题开放下载啦

去下载看看