Имеется Tkinter-приложение, в котором есть:
- Основное окно
- Дополнительное окно, которое запускается в отдельном треде. Оно содержит кнопку Panic.
Задача проста: при завершении работы программы любым способом (например, по сочетанию Alt+F4), нужно корректно завершить программу.
Я сделал минимальный пример. При завершении его работы появляется ошибка:
Запускается тред для окна с кнопкой остановки
Внутри треда с окном-кнопкой Panic
Устанавливается флаг на остановку треда с кнопкой Panic
Завершен тред _threadCodeSmallWindow
Тред с кнопкой Panic полностью завершен
Tcl_AsyncDelete: async handler deleted by the wrong thread
Аварийный останов
То есть, нужно, чтобы этой ошибки не возникало.
Если в примере закомментировать последнюю команду main.destroySmallWindow(), то ошибки не будет. Но и программа не будет завершаться по Alt+F4, потому что не завершен тред с дополнительным окном.
Я пробовал перед вызовом rootWindow.mainloop() добавить команду:
rootWindow.protocol("WM_DELETE_WINDOW", onDeleteWindow)
И функцию при закрытии писал такую:
def onDeleteWindow():
main.destroySmallWindow() # Завершение треда
rootWindow.destroy() # Закрытие основного окна
Но и тогда все равно ошибка сохраняется.
Вопрос: как корректно завершить программу?
Код примера:
#!/usr/bin/python3
import tkinter as tk
import time
from threading import Thread, Event
class MainFrame(tk.Frame):
def __init__(self, parent):
self.stopThreadFlag = Event()
self.smallWindow = None
self.threadSmallWindow = None
# Инициализация базового класса рамки
super(MainFrame, self).__init__(parent)
# Основная рамка
frame = tk.Frame(self, relief=tk.RAISED, borderwidth=1)
frame.pack(fill=tk.BOTH, expand=True)
# Надпись
label = tk.Label(frame, text="Содержимое окна")
label.pack(anchor=tk.W)
# Создается окошко-кнопка с кнопкой Panic в отдельном потоке
self._createSmallWindow()
# Создание окошка с кнопкой Panic в отдельном потоке
def _createSmallWindow(self):
print("Запускается тред для окна с кнопкой остановки")
self.stopThreadFlag.clear() # Очищается флаг прекращения работы потока
self.threadSmallWindow = Thread(target = self._threadCodeSmallWindow)
self.threadSmallWindow.start()
# Код, выполняемый внутри треда
def _threadCodeSmallWindow(self):
print("Внутри треда с окном-кнопкой Panic")
# Создается окошко с кнопкой Panic
self.smallWindow= tk.Tk() # = tk.Toplevel(Parameter().rootWindow)
self.smallWindow.resizable(0, 0) # Запрещает изменение размера
self.smallWindow.overrideredirect(1) # Отключается все оформление окона
smallWindowFrame = tk.Frame( self.smallWindow )
smallWindowFrame.pack(fill="both", expand=True)
button=tk.Button(smallWindowFrame, text="Panic", borderwidth=0, relief=tk.FLAT)
button.pack(fill=tk.BOTH, anchor="center", expand=True)
self.smallWindow.update() # Прорисовка окна с кнопкой Panic
# Вместо mainloop() используется цикл, который завершается при установке флага
while not self.stopThreadFlag.is_set():
if self.smallWindow != None:
# ... Всякие действия ...
time.sleep(0.5) # Разгрузка работы, чтобы не выжирался весь процессор
if self.smallWindow != None:
self.smallWindow.destroy()
print("Завершен тред _threadCodeSmallWindow")
# Уничтожение окна и треда с кнопкой Panic
def destroySmallWindow(self):
if self.smallWindow != None:
print("Устанавливается флаг на остановку треда с кнопкой Panic")
self.stopThreadFlag.set()
self.threadSmallWindow.join() # Ожидание завершения треда с кнопкой Panic
print("Тред с кнопкой Panic полностью завершен")
if __name__ == "__main__":
# Запуск Tk-подсистемы графических виджетов и создание основного окна
rootWindow = tk.Tk()
rootWindow.geometry("320x200+0+0")
# Создание основной рамки с интерфейсом приложения
main = MainFrame( rootWindow )
main.pack(fill="both", expand=True)
# Основной цикл GUI-интерфейса
rootWindow.mainloop()
# Вызов завершения треда с окошком Panic
# Без этого вызова программа не завершится
main.destroySmallWindow()