2013-12-23 93 views
7

我有以下代码在更新UI中的进度条(progress)时执行后台操作(scan_value)。 scan_value迭代了obj中的某个值,每次更改该值时都会发出一个信号(value_changed)。由于这里不相关的原因,我必须在另一个线程中将其包装在一个对象(Scanner)中。扫描仪在按钮scanclicked时被调用。我的问题来了......以下代码正常工作(即进度条按时更新)。PyQt:将信号连接到插槽以启动后台操作

# I am copying only the relevant code here. 

def update_progress_bar(new, old): 
    fraction = (new - start)/(stop - start) 
    progress.setValue(fraction * 100) 

obj.value_changed.connect(update_progress_bar) 

class Scanner(QObject): 

    def scan(self): 
     scan_value(start, stop, step) 
     progress.setValue(100) 

thread = QThread() 
scanner = Scanner() 
scanner.moveToThread(thread) 
thread.start() 

scan.clicked.connect(scanner.scan) 

但是,如果我改变的最后一部分,以这样的:

thread = QThread() 
scanner = Scanner() 
scan.clicked.connect(scanner.scan) # This was at the end! 
scanner.moveToThread(thread) 
thread.start() 

进度栏得到仅在结束时更新(我的猜测是,一切都在同一线程上运行)。如果在将对象接收对象移动到线程之前将信号连接到一个插槽之前,它应该是无关紧要的。

+2

看起来像ekhumoro是正确的(pyqt/qt似乎没有正确地自动检测连接类型,除非你用@pyqtSlot()明确地修饰你的插槽)。但是,我想指出'progress.setValue(100)'这一行是线程**不安全**,因为您正在从主线程以外的线程访问Qt GUI对象。其余的发布代码在Qt GUI操作 –

+1

@three_pineapples方面是线程安全的。知道这里是否存在PyQt错误,或者PyQt如何连接Python可调参数,这将是很有趣的。我知道当不使用'@ pyqtSlot'时会创建某种代理对象,但是对于排队连接究竟会产生什么后果,我不知道。 – ekhumoro

+1

@ekhumoro我认为这可能是一个PyQt4错误,或者至少应该纠正一个缺陷。它当然不会在PySide中显示出相同的行为(PySide总是在QThread中运行'scan'函数,而不管信号的位置或插槽的位置如何)。我在这里http://pastebin.com/SqP3WM1z做了一个简单的例子,它列出了正在运行的线程。 –

回答

10

连接是在将工作对象移动到另一个线程之前还是之后进行的。从Qt docs引证:

Qt的::自动连接 - 如果信号是从比接收对象不同 螺纹发射,该信号被排队时,表现为 Qt的:: QueuedConnection。否则,直接调用该插槽, 表现为Qt :: DirectConnection信号发射时确定的连接类型为 。 [强调]

所以,只要connecttype参数设置为QtCore.Qt.AutoConnection(这是默认值),Qt的应确保信号在恰当的方式发出的。

示例代码的问题更可能与插槽信号。该信号连接的蟒蛇方法可能需要被标记为Qt的插槽,使用pyqtSlot decorator

from QtCore import pyqtSlot 

class Scanner(QObject): 

    @pyqtSlot() 
    def scan(self): 
     scan_value(start, stop, step) 
     progress.setValue(100) 

编辑

应该澄清,这只是在相当最新版本的Qt发送信号时确定连接类型。在4.4版本中引入了此行为(以及对Qt多线程支持的其他更改)。

此外,值得在PyQt特定问题上进一步扩展。在PyQt中,一个信号可以连接到一个Qt插槽,另一个信号或任何可调用的python。对于后一种情况,内部创建了一个代理对象,该代理对象封装了可调用的python并提供了Qt信号/插槽机制所需的插槽。

正是这个代理对象是问题的原因。一旦代理被创建,PyQt的将简单地做到这一点:

if (rx_qobj) 
     proxy->moveToThread(rx_qobj->thread()); 

如果连接成功后的接收对象已经被移动到它的线程这是很好的;但是如果在之前制作了,代理将停留在主线程中。

装饰器使用@pyqtSlot完全避免了这个问题,因为它直接创建一个Qt插槽,根本不使用代理对象。

最后,还应该指出,这个问题目前不影响PySide。

-1

这与Qt的连接类型有关。

http://pyqt.sourceforge.net/Docs/PyQt5/signals_slots.html#connect

http://qt-project.org/doc/qt-4.8/qt.html#ConnectionType-enum

如果两个对象住在同一线程中,一个标准的连接类型制成,这导致在一个普通的函数调用。在这种情况下,耗时的操作发生在GUI线程和接口块中。

如果连接类型是消息传递样式连接,则使用在另一个线程中处理的消息来发出信号。 GUI线程现在可以自由更新用户界面。

如果未在连接功能中指定连接类型,则会自动检测类型。

+0

这似乎没有解决眼前的担忧,是吗?即便如此,你自己也注意到了默认值是auto(应该可以)。有关详情,请参阅其他答案。 – lpapp