2013-12-23 132 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