Tkinter python 3 многопоточность

Threads and tkinter

I’ve heard that threads in Python are not easy to handle and they become more tangled with tkinter. I have the following problem. I have two classes, one for the GUI and another for an infinite process. First, I start the GUI class and then the infinite process’ class. I want that when you close the GUI, it also finishes the infinite process and the program ends. A simplified version of the code is the following:

import time, threading from tkinter import * from tkinter import messagebox finish = False class tkinterGUI(threading.Thread): def __init__(self): threading.Thread.__init__(self) def run(self): global finish #Main Window self.mainWindow = Tk() self.mainWindow.geometry("200x200") self.mainWindow.title("My GUI Title") #Label lbCommand = Label(self.mainWindow, text="Hello world", font=("Courier New", 16)).place(x=20, y=20) #Start self.mainWindow.mainloop() #When the GUI is closed we set finish to "True" finish = True class InfiniteProcess(threading.Thread): def __init__(self): threading.Thread.__init__(self) def run(self): global finish while not finish: print("Infinite Loop") time.sleep(3) GUI = tkinterGUI() GUI.start() Process = InfiniteProcess() Process.start() 

When I click in the close button (in the upper right corner) the following error appears in the console:

Tcl_AsyncDelete: async handler deleted by the wrong thread 

Your simplified version works ok for me. There must be something you forgot to add that is causing your problem

@mguijarr I read in google that this error is more common in Window, what’s your SO? Mine is Windows 7 x64. Maybe windows is the problem :/

2 Answers 2

All Tcl commands need to originate from the same thread. Due to tkinter ‘s dependence on Tcl, it’s generally necessary to make all tkinter gui statements originate from the same thread. The problem occurs because mainWindow is instantiated in the tkinterGui thread, but — because mainWindow is an attribute of tkinterGui — is not destroyed until tkinterGui is destroyed in the main thread.

Читайте также:  Посчитать время выполнения программы java

The problem can be avoided by not making mainWindow an attribute of tkinterGui — i.e. changing self.mainWindow to mainWindow . This allows mainWindow to be destroyed when the run method ends in the tkinterGui thread. However, often you can avoid threads entirely by using mainWindow.after calls instead:

import time, threading from tkinter import * from tkinter import messagebox def infinite_process(): print("Infinite Loop") mainWindow.after(3000, infinite_process) mainWindow = Tk() mainWindow.geometry("200x200") mainWindow.title("My GUI Title") lbCommand = Label(mainWindow, text="Hello world", font=("Courier New", 16)).place(x=20, y=20) mainWindow.after(3000, infinite_process) mainWindow.mainloop() 

If you want to define the GUI inside a class, you can still do so:

import time, threading from tkinter import * from tkinter import messagebox class App(object): def __init__(self, master): master.geometry("200x200") master.title("My GUI Title") lbCommand = Label(master, text="Hello world", font=("Courier New", 16)).place(x=20, y=20) def tkinterGui(): global finish mainWindow = Tk() app = App(mainWindow) mainWindow.mainloop() #When the GUI is closed we set finish to "True" finish = True def InfiniteProcess(): while not finish: print("Infinite Loop") time.sleep(3) finish = False GUI = threading.Thread(target=tkinterGui) GUI.start() Process = threading.Thread(target=InfiniteProcess) Process.start() GUI.join() Process.join() 

or even simpler, just use the main thread to run the GUI mainloop:

import time, threading from tkinter import * from tkinter import messagebox class App(object): def __init__(self, master): master.geometry("200x200") master.title("My GUI Title") lbCommand = Label(master, text="Hello world", font=("Courier New", 16)).place(x=20, y=20) def InfiniteProcess(): while not finish: print("Infinite Loop") time.sleep(3) finish = False Process = threading.Thread(target=InfiniteProcess) Process.start() mainWindow = Tk() app = App(mainWindow) mainWindow.mainloop() #When the GUI is closed we set finish to "True" finish = True Process.join() 

Источник

How to use Thread in Tkinter Python?

With Tkinter, we can call multiple functions at a time using Threading. It provides asynchronous execution of some functions in an application.

In order to use a thread in Python, we can import a module called threading and subclass its Thread class. Inside our new class, we need to overwrite the Run method and perform our logic in there.

So, basically with threading, we can do multiple work at a time. To achieve threading in our application, Tkinter provides the Thread() function.

Let us take an example and create a thread which will sleep for some time and then execute another function in parallel.

For this example, we will import the Time module and the threading module defined in the Tkinter library.

Example

#Import all the necessary libraries from tkinter import * import time import threading #Define the tkinter instance win= Tk() #Define the size of the tkinter frame win.geometry("700x400") #Define the function to start the thread def thread_fun(): label.config(text="You can Click the button or Wait") time.sleep(5) label.config(text= "5 seconds Up!") label= Label(win) label.pack(pady=20) #Create button b1= Button(win,text= "Start", command=threading.Thread(target=thread_fun).start()) b1.pack(pady=20) win.mainloop()

Output

Running the above code will create a button and a thread which works on a label.

After 5 seconds, the thread will automatically pause.

Источник

Асинхронное приложение ч.1 / tkinter 15

Базовый метод предотвращения блокировки основного потока в Tkinter — это планирование действий, которые будут выполнены после истечения заданного времени.

В этом материале разберемся с тем, как реализовать этот подход в Tkinter с помощью метода after() , который может быть вызван во всех классах виджетов.

Следующий код показывает пример того, как функция обратного вызова может блокировать основной цикл.

Это приложение состоит из одной кнопки, которая становится неактивной после нажатия. Через 5 секунд ее снова можно нажать. Простейшая реализация будет выглядеть следующим образом:

 
import time
import tkinter as tk

class App(tk.Tk):
def __init__(self):
super().__init__()
self.button = tk.Button(self, command=self.start_action,
text="Ждать 5 секунд")
self.button.pack(padx=50, pady=20)

def start_action(self):
self.button.config(state=tk.DISABLED)
time.sleep(5)
self.button.config(state=tk.NORMAL) if __name__ == "__main__":
app = App()
app.mainloop()

Если запустить эту программу, то можно заметить, что не кнопка становится неактивной, а весь графический интерфейс зависает на 5 секунд. Это понятно по внешнему виду кнопки, которая в течение 5 секунд выглядит нажатой, а не выключенной. Более того, строка заголовка не будет реагировать на клики мыши все это время:

Асинхронное приложение ч.1 / tkinter 15

Если активировать дополнительные виджеты, например, Entry и Scroll, то это поведение задело бы и их.

А теперь посмотрим, как добиться нужного поведения вместо того, чтобы блокировать выполнение потока.

Как работает планирование действий

Метод after() позволяет регистрировать функцию обратного вызова, которая вызывается после задержки, заданной в миллисекундах в основном цикле Tkinter. По сути, они представляют собой зарегистрированные сигналы-события, которые обрабатываются в те моменты, когда система находится в состоянии ожидания.

Таким образом заменим вызов time.sleep(5) на self.after(5000,callback) . Используем экземпляр self , потому что метод after() также доступен в корневом экземпляре Tk , и нет разницы в том, чтобы вызывать его из дочернего виджета:

 
import tkinter as tk class App(tk.Tk):
def __init__(self):
super().__init__()
self.button = tk.Button(self, command=self.start_action,
text="Ждать 5 секунд")
self.button.pack(padx=50, pady=20)

def start_action(self):
self.button.config(state=tk.DISABLED)
self.after(5000, lambda: self.button.config(state=tk.NORMAL)) if __name__ == "__main__":
app = App()
app.mainloop()

Благодаря этому приложение будет реагировать вплоть до запланированного действия. Кнопка станет неактивной, но со строкой заголовка можно продолжать взаимодействовать привычным образом:

Планирование действий

По последнему примеру можно предположить, что метод after() исполняется после заданной в миллисекундах длительности.

Однако на самом деле метод просит Tkinter зарегистрировать событие, что гарантирует, что оно не выполнится ранее намеченного времени. И если основной поток занят, то верхнего предела того, когда оно все-таки выполнится, нет.

Нужно также помнить о том, что выполнение метода продолжается сразу же после планирования действия. Следующий пример иллюстрирует такое поведение:

 
print("Первый")
self.after(1000, lambda: print("Третий"))
print("Второй")

Этот код выведет «Первый», «Второй» и, наконец, «Третий» спустя секунду. Все это время графический интерфейс будет оставаться доступным, а пользователи смогут продолжать взаимодействовать с ним.

Обычно нужно также не допустить, чтобы одно и то же фоновое задание выполнялось более одного раза, поэтому хорошей практикой считается отключение виджета, запустившего выполнение.

Не стоит забывать, что любая запланированная функция будет выполнена в основном потоке, поэтому одного только after() недостаточно, чтобы предотвратить зависание интерфейса. Нужно также не выполнять методы, выполнение которых занимает много времени в качестве обратного вызова.

В следующем примере рассмотрим, как можно сделать так, чтобы эти блокирующие действия выполнялись в отдельных потоках.

Метод after() возвращает идентификатор запланированного события, который можно передать в метод after_cancel() для отмены выполнения функции обратного вызова.

Дальше рассмотрим, как реализовать остановку запланированного события с помощью этого метода.

Работа в потоках

Поскольку основной поток отвечает только за обновление графического интерфейса и обработку событий, оставшаяся часть фоновых событий должна выполняться на разных потоках.

Стандартная библиотека Python включает модуль threading для создания и контроля несколько потоков с помощью высокоуровневого интерфейса, который позволяет работать с простыми классами и методами.

Стоит отметить, что CPython — «эталонная реализация» Python — ограничена GIL (Global Interpreter Lock), механизмом, который не дает нескольким потокам запускать байт-код Python одновременно. Из-за этого невозможно пользоваться преимуществами многопроцессорных систем. Об этом важно помнить при попытке улучшить производительность приложения.

В следующем примере объединены приостановка потока с помощью time.sleep() , а также действие, запланированное с помощью after() :

 
import time
import threading
import tkinter as tk class App(tk.Tk):
def __init__(self):
super().__init__()
self.button = tk.Button(self, command=self.start_action,
text="Ждать 5 секунд")
self.button.pack(padx=50, pady=20)

def start_action(self):
self.button.config(state=tk.DISABLED)
thread = threading.Thread(target=self.run_action)
print(threading.main_thread().name)
print(thread.name)
thread.start()
self.check_thread(thread)

def check_thread(self, thread):
if thread.is_alive():
self.after(100, lambda: self.check_thread(thread))
else:
self.button.config(state=tk.NORMAL)

def run_action(self):
print("Запуск длительного действия. ")
time.sleep(5)
print("Длительное действие завершено!") if __name__ == "__main__":
app = App()
app.mainloop()

Как работают треды

Для создания нового объекта Thread можно использовать конструктор и аргумент-ключевое слово target . Он будет вызван на отдельном потоке при использовании его же метода start() .

В прошлом примере использовалась ссылка на метод run_action , примененная экземпляру текущего приложения:

Источник

Оцените статью