PyQt GUI를 정지하지 않고 Python에서 스레드 진행 상황을 추적하는 방법은 무엇입니까?

StackOverflow https://stackoverflow.com/questions/569650

문제

질문:

  1. GUI를 잠그지 않고 트레드의 진행 상황을 추적하기위한 모범 사례는 무엇입니까 ( "응답하지 않음")?
  2. 일반적으로 GUI 개발에 적용되는 스레딩의 모범 사례는 무엇입니까?

질문 배경:

  • Windows용 PyQt GUI가 있습니다.
  • HTML 문서 세트를 처리하는 데 사용됩니다.
  • 문서 세트를 처리하는 데 3 초에서 3 시간이 걸립니다.
  • 여러 세트를 동시에 처리 할 수 ​​있기를 원합니다.
  • GUI가 잠기는 것을 원하지 않습니다.
  • 이를 달성하기 위해 스레딩 모듈을보고 있습니다.
  • 저는 스레딩을 처음 접했습니다.
  • GUI에는 하나의 진행 표시줄이 있습니다.
  • 선택한 스레드의 진행 상황을 표시하고 싶습니다.
  • 선택한 스레드의 결과가 완료된 경우 결과를 표시합니다.
  • 저는 파이썬 2.5를 사용하고 있습니다.

내 생각: 진행률이 업데이트되면 진행률 표시줄을 업데이트하는 일부 기능을 트리거하는 QtSignal을 스레드에서 내보내도록 합니다.또한 처리가 완료되면 신호를 보내 결과를 표시할 수 있습니다.

#NOTE: this is example code for my idea, you do not have
#      to read this to answer the question(s).

import threading
from PyQt4 import QtCore, QtGui
import re
import copy

class ProcessingThread(threading.Thread, QtCore.QObject):

    __pyqtSignals__ = ( "progressUpdated(str)",
                        "resultsReady(str)")

    def __init__(self, docs):
        self.docs = docs
        self.progress = 0   #int between 0 and 100
        self.results = []
        threading.Thread.__init__(self)

    def getResults(self):
        return copy.deepcopy(self.results)

    def run(self):
        num_docs = len(self.docs) - 1
        for i, doc in enumerate(self.docs):
            processed_doc = self.processDoc(doc)
            self.results.append(processed_doc)
            new_progress = int((float(i)/num_docs)*100)

            #emit signal only if progress has changed
            if self.progress != new_progress:
                self.emit(QtCore.SIGNAL("progressUpdated(str)"), self.getName())
            self.progress = new_progress
            if self.progress == 100:
                self.emit(QtCore.SIGNAL("resultsReady(str)"), self.getName())

    def processDoc(self, doc):
        ''' this is tivial for shortness sake '''
        return re.findall('<a [^>]*>.*?</a>', doc)


class GuiApp(QtGui.QMainWindow):

    def __init__(self):
        self.processing_threads = {}  #{'thread_name': Thread(processing_thread)}
        self.progress_object = {}     #{'thread_name': int(thread_progress)}
        self.results_object = {}      #{'thread_name': []}
        self.selected_thread = ''     #'thread_name'

    def processDocs(self, docs):
        #create new thread
        p_thread = ProcessingThread(docs)
        thread_name = "example_thread_name"
        p_thread.setName(thread_name)
        p_thread.start()

        #add thread to dict of threads
        self.processing_threads[thread_name] = p_thread

        #init progress_object for this thread
        self.progress_object[thread_name] = p_thread.progress  

        #connect thread signals to GuiApp functions
        QtCore.QObject.connect(p_thread, QtCore.SIGNAL('progressUpdated(str)'), self.updateProgressObject(thread_name))
        QtCore.QObject.connect(p_thread, QtCore.SIGNAL('resultsReady(str)'), self.updateResultsObject(thread_name))

    def updateProgressObject(self, thread_name):
        #update progress_object for all threads
        self.progress_object[thread_name] = self.processing_threads[thread_name].progress

        #update progress bar for selected thread
        if self.selected_thread == thread_name:
            self.setProgressBar(self.progress_object[self.selected_thread])

    def updateResultsObject(self, thread_name):
        #update results_object for thread with results
        self.results_object[thread_name] = self.processing_threads[thread_name].getResults()

        #update results widget for selected thread
        try:
            self.setResultsWidget(self.results_object[thread_name])
        except KeyError:
            self.setResultsWidget(None)

이 접근 방식에 대한 설명(예:단점, 함정, 칭찬 등)을 높이 평가할 것입니다.

해결:

나는 스레드 간 통신을 위해 QThread 클래스와 관련 신호 및 슬롯을 사용하게 되었습니다.이는 내 프로그램이 이미 GUI 개체/위젯에 Qt/PyQt4를 사용하고 있기 때문입니다.이 솔루션을 사용하면 구현하기 위해 기존 코드를 더 적게 변경할 수도 있습니다.

다음은 Qt가 스레드와 신호를 처리하는 방법을 설명하는 해당 Qt 기사에 대한 링크입니다. http://www.linuxjournal.com/article/9602.아래에서 발췌:

다행히 QT는 스레드가 자체 이벤트 루프를 실행하는 한 신호와 슬롯을 스레드 전체에 연결할 수 있습니다.이 행사는 전송 및 수신 이벤트와 비교하여 훨씬 더 깨끗한 의사 소통 방법입니다. 왜냐하면 사소한 응용 프로그램에 필요한 모든 부기 및 중간 QEVENT 유래 클래스를 피하기 때문입니다.스레드 사이의 통신은 이제 한 스레드에서 다른 스레드의 슬롯에 신호를 연결하는 문제가되고 스레드 간 데이터 교환의 뮤트 Xing 및 스레드 안전 문제는 QT에 의해 처리됩니다.

신호를 연결하려는 각 스레드 내에서 이벤트 루프를 실행 해야하는 이유는 무엇입니까?그 이유는 한 스레드에서 다른 스레드의 슬롯에 신호를 연결할 때 QT가 사용하는 스레드 간 통신 메커니즘과 관련이 있습니다.이러한 연결이 이루어지면 대기열 연결이라고합니다.대기열 연결을 통해 신호가 방출되면 다음에 대상 객체의 이벤트 루프가 실행될 때 슬롯이 호출됩니다.대신 슬롯이 다른 스레드의 신호에 의해 직접 호출 된 경우, 해당 슬롯은 호출 스레드와 동일한 컨텍스트에서 실행됩니다.일반적으로 이것은 원하는 것이 아닙니다 (특히 데이터베이스 연결을 사용하는 경우 데이터베이스 연결을 생성 한 스레드에서만 사용할 수 있기 때문에 데이터베이스 연결을 사용하는 경우 원하는 것이 아닙니다).대기열 연결은 신호를 스레드 오브젝트로 올바르게 발산하고 이벤트 시스템에서 돼지 지원을 통해 자체 컨텍스트에서 슬롯을 호출합니다.이것은 일부 스레드가 데이터베이스 연결을 처리하는 스레드 간 통신을 위해 정확히 우리가 원하는 것입니다.QT 신호/슬롯 메커니즘은 위에서 설명한 스레드 간 이벤트 통과 체계의 구현이지만 훨씬 깨끗하고 사용하기 쉬운 인터페이스가 있습니다.

메모: 엘리벤 도 좋은 답변을 가지고 있으며 스레드 안전성과 뮤텍스를 처리하는 PyQt4를 사용하지 않았다면 그의 솔루션이 나의 선택이었을 것입니다.

도움이 되었습니까?

해결책

신호를 사용하여 메인 스레드의 진행 상황을 나타내려면 실제로 Python 스레딩 모듈의 Thread 클래스 대신 PyQt의 QThread 클래스를 사용해야 합니다.

QThread, 신호 및 슬롯을 사용하는 간단한 예는 PyQt Wiki에서 찾을 수 있습니다.

https://wiki.python.org/moin/PyQt/Threading,_Signals_and_Slots

다른 팁

UI를 손상시키는 대기열 get()을 차단해야 하기 때문에 기본 Python 대기열이 작동하지 않습니다.

Qt는 기본적으로 크로스 스레드 통신을 위해 내부에 큐잉 시스템을 구현합니다.슬롯에 호출을 게시하려면 모든 스레드에서 이 호출을 시도해 보세요.

QtCore.QMetaObject.invokeMethod()

투박하고 문서화도 잘 되어 있지 않지만 Qt가 아닌 스레드에서도 원하는 작업을 수행할 수 있습니다.

이를 위해 이벤트 기계를 사용할 수도 있습니다."post"와 같은 이름의 메소드에 대해서는 QApplication(또는 QCoreApplication)을 참조하십시오.

편집하다:더 완전한 예는 다음과 같습니다.

QWidget을 기반으로 나만의 클래스를 만들었습니다.문자열을 받아들이는 슬롯이 있습니다.나는 이것을 다음과 같이 정의합니다.

@QtCore.pyqtSlot(str)
def add_text(self, text):
   ...

나중에 기본 GUI 스레드에서 이 위젯의 ​​인스턴스를 만듭니다.메인 GUI 스레드나 다른 스레드(나무를 두드리는 소리)에서 다음을 호출할 수 있습니다.

QtCore.QMetaObject.invokeMethod(mywidget, "add_text", QtCore.Q_ARG(str,"hello world"))

투박하지만 거기에 도달합니다.

단.

신호 대신 대기열을 사용하는 것이 좋습니다.개인적으로 저는 이 방법이 동기식이기 때문에 훨씬 더 강력하고 이해하기 쉬운 프로그래밍 방식이라고 생각합니다.

스레드는 대기열에서 "작업"을 가져와서 다른 대기열에 결과를 다시 넣어야 합니다.그러나 오류 및 "진행 보고서"와 같은 알림 및 메시지를 위해 스레드에서 세 번째 대기열을 사용할 수 있습니다.이런 식으로 코드를 구성하면 관리가 훨씬 더 간단해집니다.

이러한 방식으로 단일 "작업 대기열"과 "결과 대기열"은 작업자 스레드 그룹에서도 사용할 수 있으며 스레드의 모든 정보를 기본 GUI 스레드로 라우팅합니다.

"processDoc" 메서드가 다른 데이터를 변경하지 않는 경우(일부 데이터를 찾아 반환하고 상위 클래스의 변수나 속성을 변경하지 않음) Py_BEGIN_ALLOW_THREADS 및 Py_END_ALLOW_THREADS 매크로를 사용할 수 있습니다( 자세한 내용은 여기를 참조하세요 ) 그 안에.따라서 문서는 인터프리터를 잠그지 않고 UI가 업데이트되는 스레드에서 처리됩니다.

Python에서는 항상 이런 문제가 발생합니다.자세한 배경 정보를 보려면 Google GIL "전역 해석기 잠금"을 참조하세요.현재 발생한 문제를 해결하기 위해 일반적으로 권장되는 두 가지 방법이 있습니다.사용 꼬인, 또는 다음과 유사한 모듈을 사용하십시오. 다중 처리 2.5에서 소개된 모듈입니다.

Twisted를 사용하려면 처음에는 혼란스러울 수 있는 비동기 프로그래밍 기술을 배워야 하지만 처리량이 높은 네트워크 앱을 작성해야 하는 경우 도움이 되고 장기적으로는 더 유익할 것입니다.

멀티프로세싱 모듈은 새로운 프로세스를 분기하고 IPC를 사용하여 실제 스레딩이 있는 것처럼 작동하도록 만듭니다.유일한 단점은 대부분의 Linux 배포판이나 OSX에 기본적으로 포함되어 있는 상당히 새로운 Python 2.5를 설치해야 한다는 것입니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top