Как использовать модуль threading в приложениях Tkinter в Python
В приложении Tkinter основной цикл всегда должен начинаться в основном потоке. Он отвечает за обработку событий и обновление GUI.
Если у вас есть фоновая операция, которая требует времени, ее следует выполнить в отдельном потоке. В противном случае приложение не будет реагировать. В худшем случае оно зависнет во время выполнения операции.
Когда использовать Thread в приложениях Tkinter
Для создания и управления несколькими потоками в приложениях Tkinter можно использовать threading — модуль потоковой обработки в Python.
Модуль threading данных включен в стандартную библиотеку Python, поэтому вам не нужно его устанавливать. Дополнительную информацию об использовании модуля потоков можно найти в руководстве по работе с потоками в Python.
Пример Tkinter thread
Мы создадим простую программу, которая загружает веб-страницу, указанную по URL-адресу, и отображает ее содержимое в виджете Text:
Для загрузки веб-страницы мы воспользуемся модулем запросов requests.
Сначала установите модуль 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
Затем определите новый класс с именем AsyncDownload, который наследует от класса Thread:
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:
- В методе __init__() класса AsyncDownload мы инициализируем атрибуты html и url.
- В методе run() мы вызываем функцию get() для загрузки веб-страницы, указанной в URL-адресе, и назначаем исходный HTML-код атрибуту html.
После этого создайте класс App, унаследованный от класса Tk. Класс App представляет корневое окно.
Корневое окно состоит из трех фреймов, которые содержат все виджеты. Мы не будем фокусироваться на том, как создавать виджеты и размещать их в окне с помощью менеджера геометрии сетки.
При нажатии кнопки загрузки программа выполняет метод handle_download() класса App.
В методе 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() мы планируем действие, которое запустит метод monitor() через 100 мс, если поток все еще активен.
Если загрузка завершена, мы обновляем содержимое виджета 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()