본문 바로가기
에러 해결

pyside GUI 버튼 클릭 시 에러 없이 창이 꺼짐

by Devry 2023. 7. 28.

🛠환경

윈도우 10 최신
파이썬 3.11
Pyside6  6.4.2
pyinstaller 5.13.0
Selenium 4.8.2

상황

Pyinstaller로 빌드된 프로그램에서 버튼을 클릭 시 에러 없이 프로그램이 종료되는 상황입니다.

버튼 클릭 시에는 Selenium으로 웹페이지 동작을 자동화합니다.

 

원인 분석

  1. 에러를 출력해야 구글링이 가능해서 try문으로 소스코드 전체부터 메서드 하나까지 여러 번 감싸도 출력이 안 됐습니다.
  2. "버튼 클릭 시에 크롬 드라이버가 불러와지는 딜레이 시간 때문에 발생한다"라고 생각은 했지만 다소 억지가 있습니다.
  3. 이상한 점은 초기 실행 시에 이 현상이 자주 나타나고, 그 이후는 덜 발생한다는 점입니다. 코드 상의 에러면 100% 발생해야 하지만 이러한 간헐적인 에러는 통신과 관련되었다고 생각이 들게 만들었습니다.

 

🔑해결

 

콘솔 자체도 종료가 된다는 점이 더 근본적인 에러라는 생각이 들어서 애초에 구현 방식에 문제가 있는지에 대해 ChatGPT에 질문한 결과,

메인 스레드가 아닌 스레드에서 GUI를 변경하면 종료가 될 수 있다.

라는 답변에서 제가 잘못된 방식으로 스레드를 조작했다는 것을 알 수 있었습니다.

 

기존 방식

 

class MyMainWindow(QMainWindow, Ui_MainWindow):
    def __init__(self, parent=None):
        super(MyMainWindow, self).__init__(parent)
        self.setupUi(self)
        
        # 버튼 연결
        self.pushButton.clicked.connect(self.button_clicked)
        
    def button_clicked(self):
        self.work_thread = WorkerThread(self)
        self.work_thread.finished.connect(self.finished_work)
        self.work_thread.start()    
        
        
class WorkerThread(QtCore.QThread):
    def __init__(self, main_window):
        super().__init__()
        self.main_window = main_window
    def run(self):
    	# 어떤 작업
        self.main_window.pushButton.setEnabled(False) # 에러의 원인

간단하게 2개의 클래스를 구성했고 MyMainWindow 클래스의 버튼을 클릭하면 WorkerThread라는 스레드를 생성하여 동작하는 프로그램입니다.

이전에도 self.main_window = main_window의 방식으로 메인 윈도우의 UI 요소를 받아서 사용했지만 상태를 변경하는 게 아니기 때문인지 발생하지 않았던 것 같습니다.

self.main_window.pushButton.setEnabled(False)는 버튼을 연속적으로 클릭하지 못하도록 비활성화하는 부분인데 이 부분이 원인이라고는 에러가 뜨지 않으면 도저히 생각해 내기 힘듭니다.

 

수정한 방식

class MyMainWindow(QMainWindow, Ui_MainWindow):
    def __init__(self, parent=None):
        super(MyMainWindow, self).__init__(parent)
        self.setupUi(self)
        
        # 버튼 연결
        self.pushButton.clicked.connect(self.button_clicked)
        
    def button_clicked(self):
    	self.pushButton.setEnabled(False)    # 버튼 비활성화를 메인 스레드에서 처리
        self.work_thread = WorkerThread(self)
        self.work_thread.finished.connect(self.finished_work)
        self.work_thread.start()    
        
    def finished_work(self): 
    	self.pushButton.setEnabled(True) # 완료 시그널을 통해 버튼 활성화
        
class WorkerThread(QtCore.QThread):
    def __init__(self, main_window):
        super().__init__()
        self.main_window = main_window
    def run(self):
    	# 어떤 작업
        self.main_window.pushButton.setEnabled(False) # 에러의 원인

기존에 버튼 비활성화를 WorkerThread의 run 메서드에서 MyMainWindow의 button_clicked 메서드 내부로 옮겼습니다.

다시 활성화 부분은 finished_work 메서드로 옮기고 WorkerThread의 finished 시그널과 연결했습니다.

finished 시그널은 QThread에 자동으로 생성되는 시그널로, 생성된 스레드가 종료되면 자동으로 발생합니다.

 

이외에도 스레드 간에 통신은 인자로 넘겨주는 게 아닌, 시그널을 발생시키고 해당 스레드 내부에서 처리하도록 구현해야 합니다.

 

 

 

댓글