在PyQt QThreads中同步活动 [英] Syncing activity in PyQt QThreads

查看:83
本文介绍了在PyQt QThreads中同步活动的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm playing around with PyQt, and QThreads. It seems that if I use the code that I've put in this python fiddle (note that the top section is auto-generated code from QtDesigner), where the current value of the loop is printed both in the loop in the slave thread and the loop controlling the progress bar, then the loops keep in sync, the values match at all points as the program runs, and the progress bar displays accurately the proportion of the slave thread completed.

作为对下面评论的回应,处于当前状态的该程序实际上执行了我想要的操作-它只是将从属线程中循环的值和其中控制进度条的进度.

In response to a comment below, this program in its current state actually does what I want it to - it just prints out, to the terminal, the value of the loop in the slave thread and the the value in the loop which controls the progression of the progress bar.

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

However, commenting out line 121 (i.e. if you don't print the current value in the progress bar loop), results in the progress bar reaching 100% (i.e. finishing 300 iterations) when the slave thread loop has only reached ~130 iterations (i.e. the progress bar completes about 100% faster).

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

Have I done something naïvely stupid / wrong - is there a better way to complete what I want to do?!

推荐答案

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

Just because you have two loops doing the same number of iterations in different threads, does not mean they'll take the same length of time to complete. Generally, the contents of each iteration will take some amount of time to complete, and since your loops are doing different things, they'll (generally) take different lengths of time.

此外,借助Python中的线程,Global-Interpreter-Lock(GIL)阻止了线程在多核CPU上同时运行. GIL负责在线程之间进行切换,并继续执行,然后再切换到另一个,然后再切换到另一个,依此类推.对于QThreads,这变得更加复杂,因为QThread中的Qt调用可以在没有GIL的情况下运行(所以我理解),但一般的python代码仍将与GIL一起运行.

Further more, with threads in Python, The Global-Interpreter-Lock (GIL) blocks threads from running simultaneously on a multi-core CPU. The GIL is responsible for switching between threads, and continuing their execution, before switching to another, and then another, etc., etc. With QThreads, this becomes even more complex because Qt calls in a QThread can run without the GIL (so I understand) but general python code will still run with the GIL.

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

Because the GIL is responsible for handling what thread is running at any given time, I've even seen two threads, doing exactly the same thing, run at different speeds. As such, it is an entire coincidence that your two threads ever finished at the same time!.

请注意,由于GIL的原因,两个CPU密集型任务无法在单独的线程中运行而带来速度上的好处.为此,您需要使用多重处理.但是,如果您要输出受I/O约束的任务(例如,通过主线程中的GUI进行用户界面操作,在另一个线程中进行网络通信,也就是通常花费大量时间来等待程序外部内容的任务),触发一些东西),那么线程很有用.

Note, that because of the GIL, two cpu-instensive tasks will have no speed benefit from running in separate threads. For that, you need to use multi-processing. However, if you want to farm out I/O bound tasks (such as user interface through a GUI in the main thread, network communication in another thread, aka, tasks that often spend a lot of time waiting for something outside of the program to trigger something) then threading is useful.

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

So, hopefully that helps explain threading, and what is going on in your program.

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

There are a couple of ways to do this better. One would be to keep the loop in your thread, but remove the other. Then use the qt signal/slot mechanism to call a function in MainWindow which runs one iteration of the loop that used to be there. This doesn't guarantee synchronisation though, only that your QThread will finish first (something could slow down the main thread so that events pile up and the function in MainWindow doesn't run until later). To complete the synchronisation, you could use a threading.Event object to make the QThread wait until the new function in MainWindow has run.

示例(未经测试,很抱歉,但希望能给出这个主意!):

Example (untested, sorry, but hopefully gives the idea!):

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()装饰器的原因是

Note the use of @QtCore.pyqtSlot() decorator is because of the issue documented here. In short, when you use signal.connect(my_function), you are omitting the second argument which determines the behaviour of the slot (whether it is executed immediately when signal.emit() is called, or whether it is executed once control returns to the event loop (aka, placed in a queue to be run later)). By default, this optional argument to connect tries to automatically decide which type of connection to make (see here) which works usually. However if the connection is made before it knows that it is a connection between threads, and the "slot" is **not* explicitly defined as a slot using @pyqtSlot, pyQT gets confused!

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

Additional info on decorators: The simplest way to think of decorators is a shorthand for wrapping a function in another function. The decorator replaces your defined function with its own, and this new function usually uses your original function at some point. So in the @pyqtSlot case, the signal emission actually calls a pyqt function generated by @pyqtSlot and this function eventually calls the original function you wrote. The fact that the @pyqtSlot decorator takes arguments that match the types of the arguments of your slot, is an implementation detail of the pyqt decorator, and not representative of decorators in general. It is simply stating that your slot expects to be passed data of the specified types by a connected signal.

这篇关于在PyQt QThreads中同步活动的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆