2014-12-30 3 views
0

Tkinter에 작성한 응용 프로그램을 PyQt에 이식하려고합니다. 응용 프로그램에는 API를 통해 외부 정보를 쿼리하고이를 사용하여 응용 프로그램의 텍스트 레이블을 업데이트하는 여러 스레드가 있습니다. 사용자 상호 작용은 없습니다.메서드에서 여러 스레드를 시작하는 방법?

Tkinter에서 나는 after() 방법을 사용하고있었습니다. PyQT에서 내가 이해하는 한, QtCore.QThread()을 사용해야합니다.

다음 예제 프로그램은 두 개의 레이블 (hourminute)을 업데이트해야합니다. UI는 Qt Designer에서 생성되며 기본적으로 위의 두 텍스트 레이블입니다. 앱 윈도우에서 시간이 업데이트되고 (GUI에서 변경되며 콘솔에 "update hour"이 인쇄 됨) 나타납니다. 분은 그렇지 않습니다. 내 (실패한) 이해는 started.connect()이 표시된 방법으로 스레드를 시작하는 것이 었습니다. 한 번만 작동하는 것처럼 보입니다.

UpdateTime의 각 메소드에 대해 스레드가 시작되도록 코드를 변경해야합니까?

import sys 
import time 

from PyQt4 import QtGui, QtCore 
from infoscren_ui import Ui_MainWindow 

class Infoscreen(QtGui.QMainWindow, Ui_MainWindow): 
    def __init__(self): 
     QtGui.QMainWindow.__init__(self) 
     self.setupUi(self) 


class UpdateTime(QtCore.QObject): 
    def __init__(self, root): 
     QtCore.QObject.__init__(self) 
     self.root = root 

    def UpdateHour(self): 
     hour = 0 
     while True: 
      hour += 1 
      print("update hour") 
      self.root.hour.setText(str(hour)) 
      time.sleep(1) 

    def UpdateMinute(self): 
     minute = 0 
     while True: 
      minute += 2 
      print("update minute") 
      self.root.minute.setText(str(minute)) 
      time.sleep(1) 


if __name__ == "__main__": 
    app = QtGui.QApplication(sys.argv) 
    infoscreen = Infoscreen() 
    thread = QtCore.QThread() 

    obj = UpdateTime(infoscreen) 
    obj.moveToThread(thread) 
    thread.started.connect(obj.UpdateHour) 
    thread.started.connect(obj.UpdateMinute) 
    thread.finished.connect(app.exit) 
    thread.start() 

    infoscreen.show() 
    sys.exit(app.exec_()) 

답변

2

같은 스레드에서 UpdateHourUpdateMinute을 모두 실행할 수 없습니다. 처음 시작하는 것은 다른 스레드를 막기 때문입니다. 이와 같이 두 개의 블로킹 루프를 실행하려면 각각의 블로킹 루프가 필요합니다.

또한 GUI GUI 스레드 (즉, 응용 프로그램이 처음 시작되는 스레드) 외부에서 GUI 메소드를 직접 호출하는 것은 스레드로부터 안전하지 않습니다. 메인 스레드에서

class UpdateHours(QtCore.QObject): 
    hourChanged = QtCore.pyqtSignal(object) 

    @QtCore.pyqtSlot() 
    def updateHour(self): 
     ... 
     self.hourChanged.emit(hour) 

객체는 다음이 신호에 연결할 수 있습니다 거기에서 GUI 업데이트 : 대신, 당신은 작업자 스레드에서 방출되는 사용자 정의 신호를 정의해야합니다 기본적으로

self.hourObject.hourChanged.connect(self.handleHourChanged) 
    ... 

    def handleHourChanged(self, hour): 
     # do something with hour value... 

을, 발신자와 수신자가 서로 다른 쓰레드에있을 때, Qt는 신호가 비동기 적으로 처리되도록 보장합니다 (애플리케이션의 이벤트 대기열에 추가됨). 그러면 스레드 안전성을 보장하기에 충분합니다.

+0

고맙습니다. 내가 이해하지 못하는 한가지가 있습니다 : 객체의 메소드 (당신의 예제에서'updateHour')는'.moveToThread (thread)'를 통해 스레드로 어떻게 이동 되었습니까? 이 개체의 모든 메서드가 기본적으로 시작 되었습니까? 나는 당신의 코드도, Schollii의 것도 언급하지 않았다. – WoJ

+0

@WoJ. 원래 예제처럼 스레드의 'started'신호를 사용하는 것이 가장 자연 스럽습니다. 아마도 그러한 방법을 슬롯으로 꾸미는 것이 현명 할 것입니다.하지만 그에 대한 대답은 업데이트되었습니다. – ekhumoro

1

GUI는 메인 스레드에서 실행됩니다. 그것이 app.exec()가 호출되는 곳입니다. 생성 한 QThread 인스턴스는 "보조"스레드를 나타냅니다. 주 스레드가 아닌 스레드에서 QObject의 메서드 (예 : QLabel.setText)를 호출하지 마십시오. 대신 클래스에서 사용자 정의 신호를 정의하여 보조 스레드로 이동시킨 다음 GUI 객체의 슬롯에 연결하십시오. 그런 다음, 신호가 보조 스레드에서 방출 되더라도 PyQt는 주 스레드의 슬롯을 호출합니다. 순진 구현이 다음과 같이 보일 것입니다 :

class UpdateTime(QtCore.QObject): 
    sig_minute = pyqtSignal(str) 
    sig_hour = pyqtSignal(str) 

    def __init__(self): 
     QtCore.QObject.__init__(self) 

    def UpdateHour(self): 
     hour = 0 
     while True: 
      hour += 1 
      print("update hour") 
      self.sig_hour.emit(str(hour)) 
      time.sleep(1) 

    def UpdateMinute(self): 
     minute = 0 
     while True: 
      minute += 2 
      print("update minute") 
      self.sig_minute.emit(str(minute)) 
      time.sleep(1) 

당신은 주에있는 연결을 설정할 수 있습니다 :이 UpdateTime에서 InfosSreen을 분리 얼마나

if __name__ == "__main__": 
    app = QtGui.QApplication(sys.argv) 
    infoscreen = Infoscreen() 
    thread = QtCore.QThread() 

    obj = UpdateTime() 
    obj.moveToThread(thread) 
    obj.sig_hour.connect(infoscreen.hour.setText) 
    obj.sig_minute.connect(infoscreen.minute.setText) 
    thread.finished.connect(app.exit) 
    thread.start() 

    infoscreen.show() 
    sys.exit(app.exec_()) 

참고. hourminute 레이블이 InfoScreen의 구성원이기 때문에 더 나은 캡슐화를 수행 할 수 있습니다. 대신 연결을 설정할 수 있습니다. 또는 하나는 UpdateTime.__init__에 연결을 설정할 수 있지만 Qt에서는 비주얼이 주변의 다른 방법보다 작업자 (논리, ​​컨트롤러 등)에 대해 알 수있는 철학 인 것 같습니다.

어떤 경우에도 moveToThread 다음에 연결을 설정하는 것이 중요합니다. 기본 연결 유형이 AUTO이지만 이것은 연결이 이루어질 때 Qt가 연결할 연결 유형을 결정한다는 것을 의미합니다 (차단 또는 대기 중). 따라서 InfoScreenUpdateTimemoveToThread 전에 연결하면 Qt는 같은 스레드에서 "살아"있다고 생각하고 연결을 차단합니다. moveToThread이 호출 된 후에도 연결은 계속 차단되므로 보조 스레드에서 실행되는 함수에 의해 생성 된 신호는 InfosScreen의 연결된 슬롯을 주 스레드가 아닌 동일한 스레드에서 호출합니다. 반면에 UpdateTime이 처음 스레드로 이동되면 ,이 연결되면 Qt는 그것이 메인 스레드가 아닌 것으로 인식하여 연결을 대기시킵니다. 이 경우 보조 스레드에서 신호를 내보내면 연결된 슬롯이 주 스레드에서 호출됩니다.

그러나 개체를 스레드로 이동해도 개체가 실행되지 않으므로 위의 작업은 아무 것도하지 않습니다. 어떤 스레드가 실행되는지는 QThread.start()이 호출되면됩니다.따라서 객체가 작동하도록하려면 객체의 슬롯을 호출하여 슬롯이 보조 스레드에서 실행되도록해야합니다. QThread 인스턴스가 메인 스레드에 살고있는 바로 그겁니다

thread.started.connect(obj.run) 

그런 다음, 그 started 신호가 비동기 적으로, 보조 스레드에서 처리됩니다 : 한 가지 방법은 moveToThread 후 연결 스레드의 start 신호를 통해입니다. obj.run 메서드는 UpdateHourUpdateMinute을 적절하게 호출 할 수 있습니다. 그러나 UpdateHourUpdateMinute은 모두 동일한 스레드에서 무기한으로 반복 할 수 없습니다 ( while True 사용). 두 개의 개별 스레드를 두 개의 개별 스레드 (하나는 1 시간, 다른 하나는 분)에 생성하거나 두 업데이트를 작은 시간 동안 실행하여 공동 작업을 수행해야합니다. 세부 사항은 TK에서 포팅 어떤 기능에 의존하지만, 후자의 방법의 예를 들어, 다음과 같이 수 :

class UpdateTime(QtCore.QObject): 
    sig_minute = pyqtSignal(str) 
    sig_hour = pyqtSignal(str) 

    def __init__(self): 
     super().__init__(self) 
     self.hour = 0 
     self.minute = 0 
     self.seconds = 0 

    def run(self): 
     while True: 
      time.sleep(1) 
      self.seconds += 1 
      self.UpdateMinute() 
      self.UpdateHour() 

    def UpdateHour(self): 
     if self.minute == 60: 
      self.hour += 1 
      print("update hour") 
      self.sig_minute.emit(str(self.hour)) 
      self.minute = 0 

    def UpdateMinute(self): 
     if self.seconds == 60: 
      self.minute += 1 
      print("update minute") 
      self.sig_minute.emit(str(self.minute)) 
      self.seconds = 0 

대신 obj.run를 호출 started 신호를 연결하는, 당신이 QTimer 년대를 연결할 수 있습니다 timeout 대신 단발 모드에서 신호를 보내고 나중에 obj.run이 한 번 호출됩니다. 또는 버튼의 clicked 신호를 연결하여 버튼을 클릭 할 때만 신호를 시작하십시오. 귀하의 경우 UpdateTimeQObject.startTimer을 호출하고 eventTimer을 무시하여 1 분을 증가시켜 구현할 수 있습니다. 타이머에 대한 자세한 내용은 Qt timers을 참조하십시오.

버그에 대해서는 위 테스트를하지 않았지만 아이디어를 얻었습니다.

+0

의견을 보내 주셔서 감사합니다. 조금만 확대 해 주시겠습니까? "주 스레드"란 무엇입니까? 그것은 app.exec _()를 통해 시작된 것입니까? 작업자 스레드를 시작하여 적절한 신호를 보낼 수 있도록해야합니다 (주 스레드에 - 가정). 코드에서 시도한 방식이 작동하지 않습니다 (첫 번째 만 시작됨). – WoJ

+0

자동 연결에서는 연결 유형이 아닌 신호가 방출 될 때 연결 유형이 결정됩니다. 리시버가 Qt 슬롯이거나'@ QtCore.pyqtSlot'으로 꾸며진 파이썬 호출 가능 모듈이라면 언제든지 연결할 수 있습니다. – ekhumoro

+0

@Schollii : 초기 답변을 확대 해 주셔서 감사합니다. 나는 ekhumoro와 같은 질문을 가지고있다 : 객체의 메소드 ('updateHour'와'updateMinute')는'.moveToThread (thread)'를 통해 스레드로 어떻게 이동 되었습니까? 이 개체의 모든 메서드가 기본적으로 시작 되었습니까? – WoJ

관련 문제