Tkinter Thread

2024. 3. 31. 17:16GUI/tkinter

요약 : 이 튜토리얼에서는 Tkinter 애플리케이션에서 다중 스레드를 사용하여 애플리케이션의 반응성을 높이는 방법을 배웁니다.

 

Tkinter 애플리케이션에서 Thread를 사용해야 하는 경우

 

Tkinter 애플리케이션에서 메인 루프는 항상 메인 스레드에서 시작되어야 합니다. 이벤트 처리 및 GUI 업데이트를 담당합니다.

시간이 걸리는 백그라운드 작업이 있는 경우 별도의 스레드 에서 실행해야 합니다.

그렇지 않으면 응용 프로그램이 응답하지 않습니다. 최악의 경우 작업이 실행되는 동안 정지됩니다.

Tkinter 애플리케이션에서 여러 스레드를 생성하고 제어하려면 Pythonthreading 모듈을 사용할 수 있습니다 .

모듈 threading은 Python의 표준 라이브러리에 포함되어 있으므로 설치할 필요가 없습니다.

스레딩 모듈을 사용하는 방법에 대한 자세한 내용은 Python 스레딩 튜토리얼을 참조하세요.

 

Tkinter 스레드 예

 

URL로 지정된 웹페이지를 다운로드하고 그 내용을 Text 위젯에 표시하는 간단한 프로그램을 작성하겠습니다.

 

 

웹페이지를 다운로드하기 위해 요청 모듈을 사용합니다 .

먼저 다음 명령을 실행하여 requests 모듈을 설치 합니다.

 
pip install requests
 

다음으로 tkinter, threading, 및 requests모듈을 가져옵니다.

 
import tkinter as tk
from tkinter import ttk
from tkinter.messagebox import showerror
from threading import Thread
import requests
 

그런 다음 Thread 클래스에서 상속되는 AsyncDownload를 호출하는 새 클래스를 정의합니다.

 
class AsyncDownload(Thread):
    def __init__(self, url):
        super().__init__()
        self.html = None
        self.url = url

    def run(self):
        response = requests.get(self.url)
        self.html = response.text
 

AsyncDownload class 동작 방식:

  • AsyncDownload 클래스의 __init__() 메소드에서 html 및 url 속성을 초기화합니다.
  • run() 메서드에서는 get() 함수를 호출하여, URL로 지정된 웹페이지를 다운로드하고 html 소스 코드를 HTML 속성에 할당합니다.

그런 다음 App클래스에서 상속되는 Tk 클래스를 만듭니다. App 클래스는 루트 창을 나타냅니다.

루트 창은 모든 위젯을 보유하는 세 개의 프레임으로 구성됩니다. 그리드 지오메트리 관리자를 사용하여 위젯을 생성하고 창에 배치하는 방법에 대해서는 집중하지 않겠습니다 .

다운로드 버튼을 클릭하면, 프로그램이 App 클래스의 handle_download() 메소드를 실행합니다.

이 handle_download() 메서드에서는, URL이 제공되었는지 확인합니다. 예이면 AsyncDownload 클래스의 새 인스턴스를 만들고 스레드를 시작합니다. 또한 다운로드 버튼을 비활성화하고 Text 위젯의 내용을 지웁니다.

또한 스레드 상태를 모니터링하는 monitor() 메서드를 호출합니다.

 
def handle_download(self):
    url = self.url_var.get()
    if url:
        self.download_button['state'] = tk.DISABLED
        self.html.delete(1.0, "end")

        download_thread = AsyncDownload(url)
        download_thread.start()

        self.monitor(download_thread)
    else:
        showerror(title='Error',
                message='Please enter the URL of the webpage.')
 

monitor() 메서드에서는 스레드가 아직 살아 있는 경우, 100ms 후에 monitor() 메서드를 실행하는 작업을 예약합니다.

다운로드가 완료되면 Entry 위젯의 콘텐츠를 업데이트하고 다운로드 버튼을 다시 활성화합니다.

 
def monitor(self, thread):
    if thread.is_alive():
        # check the thread every 100ms
        self.after(100, lambda: self.monitor(thread))
    else:
        self.html.insert(1.0, thread.html)
        self.download_button['state'] = tk.NORMAL
 

마지막으로 애플리케이션의 메인 루프를 실행합니다.

 

if __name__ == "__main__":
     app = App()
     app.mainloop()

 

다음은 전체 프로그램을 보여줍니다.

 
import tkinter as tk
from tkinter import ttk
from tkinter.messagebox import showerror
from threading import Thread
import requests

class AsyncDownload(Thread):
    def __init__(self, url):
        super().__init__()
        
        self.html = None
        self.url = url
        
    def run(self):
        response = requests.get(self.url)
        self.html = response.text
        
class App(tk.Tk):
    def __init__(self):
        super().__init__()
        
        self.title('Webpage Download')
        self.geometry('680x430') 
        self.resizable(0, 0)
        
        self.create_header_frame()
        self.create_body_frame()
        self.create_footer_frame()
        
    def create_header_frame(self):
 
        self.header = ttk.Frame(self)
        # configure the grid
        self.header.columnconfigure(0, weight=1)
        self.header.columnconfigure(1, weight=10)
        self.header.columnconfigure(2, weight=1)
        # label
        self.label = ttk.Label(self.header, text='URL')
        self.label.grid(column=0, row=0, sticky=tk.W)

        # entry
        self.url_var = tk.StringVar()
        self.url_entry = ttk.Entry(self.header,
                                   textvariable=self.url_var,
                                   width=80)

        self.url_entry.grid(column=1, row=0, sticky=tk.EW)

        # download button
        self.download_button = ttk.Button(self.header, text='Download')
        self.download_button['command'] = self.handle_download
        self.download_button.grid(column=2, row=0, sticky=tk.E)

        # attach the header frame
        self.header.grid(column=0, row=0, sticky=tk.NSEW, padx=10, pady=10)
        
    def handle_download(self):
        url = self.url_var.get()
        if url:
            self.download_button['state'] = tk.DISABLED
            self.html.delete(1.0, "end")

            download_thread = AsyncDownload(url)
            download_thread.start()

            self.monitor(download_thread)
        else:
            showerror(title='Error',
                      message='Please enter the URL of the webpage.')

    def monitor(self, thread):
        if thread.is_alive():
            # check the thread every 100ms
            self.after(100, lambda: self.monitor(thread))
        else:
            self.html.insert(1.0, thread.html)
            self.download_button['state'] = tk.NORMAL

    def create_body_frame(self):
        self.body = ttk.Frame(self)
        # text and scrollbar
        self.html = tk.Text(self.body, height=20)
        self.html.grid(column=0, row=1)

        scrollbar = ttk.Scrollbar(self.body,
                                  orient='vertical',
                                  command=self.html.yview)

        scrollbar.grid(column=1, row=1, sticky=tk.NS)
        self.html['yscrollcommand'] = scrollbar.set

        # attach the body frame
        self.body.grid(column=0, row=1, sticky=tk.NSEW, padx=10, pady=10)

    def create_footer_frame(self):
        self.footer = ttk.Frame(self)
        # configure the grid
        self.footer.columnconfigure(0, weight=1)
        # exit button
        self.exit_button = ttk.Button(self.footer,
                                      text='Exit',
                                      command=self.destroy)

        self.exit_button.grid(column=0, row=0, sticky=tk.E)

        # attach the footer frame
        self.footer.grid(column=0, row=2, sticky=tk.NSEW, padx=10, pady=10)


if __name__ == "__main__":
    app = App()
    app.mainloop()        
 

요약

 
  • Tkinter 애플리케이션이 반응하도록 하려면 별도의 스레드에서 백그라운드 작업을 실행하십시오.