Поскольку я застрял в той же проблеме и не нашел надлежащего, хорошо объясненного решения, я хотел бы поделиться базовой стратегией, с которой я столкнулся.
Обратите внимание, что это не единственный и не лучший способ сделать многопоточность с помощью tkinter, но он довольно прост и должен сохранить ваш рабочий процесс, если вы разрабатывали свой код, не зная о поточности потоков в tkinter.
Почему темы?
Прежде всего, я решил использовать потоки, видя, что блокирующие действия, такие как os.popen
, subprocess.call
, time.sleep
и т. П. «заморозить» графический интерфейс, пока он не запустится (конечно, это может быть не так, поскольку потоки полезны сами по себе по многим причинам, а иногда они просто необходимы).
Вот так выглядел мой код перед использованием потоков:
from Tkinter import *
import tkMessageBox
from time import sleep
# Threadless version.
# Buttons will freeze the GUI while running (blocking) commands.
def button1():
sleep(2)
tkMessageBox.showinfo('title', 'button 1')
def button2():
sleep(2)
tkMessageBox.showinfo('title', 'button 2')
root = Tk()
frame = Frame(root)
frame.pack()
Frame(root).pack( side = BOTTOM )
Button(frame, command=button1, text="Button 1").pack( side = LEFT )
Button(frame, command=button2, text="Button 2").pack( side = LEFT )
root.mainloop()
Багги с резьбой
Затем я превратил команды, вызываемые кнопками, в потоки. Таким образом, графический интерфейс не зависнет.
Я думал, что все в порядке, но в Windows этот код приводит к непоправимому сбою интерпретатора из-за того, что tkMessageBox
вызывается из потоков, отличных от того, в котором работает корень tkinter:
from Tkinter import *
import tkMessageBox
from time import sleep
import threading
# Buggy threads.
# WARNING: Tkinter commands are run into threads: this is not safe!!!
def button1():
sleep(2)
tkMessageBox.showinfo('title', 'button 1')
def button2():
sleep(2)
tkMessageBox.showinfo('title', 'button 2')
def start_thread(fun, a=(), k={}):
threading.Thread(target=fun, args=a, kwargs=k).start()
root = Tk()
frame = Frame(root)
frame.pack()
Frame(root).pack( side = BOTTOM )
Button(frame, command=lambda: start_thread(button1), text="Button 1").pack( side = LEFT)
Button(frame, command=lambda: start_thread(button2), text="Button 2").pack( side = LEFT )
root.mainloop()
Потоковая версия
Когда я обнаружил отсутствие потока в tkinter, я написал небольшую функцию tkloop
, которая будет запускаться в основном потоке каждые несколько миллисекунд, проверяя запросы и выполняя запрошенные (tkinter) функции от имени потоков, которые хотят их запустить .
Двумя ключами здесь является метод widget.after
, который " регистрирует функцию обратного вызова, которая будет вызываться через определенное количество миллисекунд " и Queue
ставить и получать запросы.
Таким образом, поток может просто поместить кортеж (function, args, kwargs)
в очередь вместо вызова функции, что приводит к безболезненному изменению исходного кода.
Это последняя, поточно-ориентированная версия:
from Tkinter import *
import tkMessageBox
from time import sleep
import threading
from Queue import Queue
# Thread-safe version.
# Tkinter functions are put into queue and called by tkloop in the main thread.
q = Queue()
def button1():
sleep(2)
q.put(( tkMessageBox.showinfo, ('title', 'button 1'), {} ))
def button2():
sleep(2)
q.put(( tkMessageBox.showinfo, ('title', 'button 2'), {} ))
def start_thread(fun, a=(), k={}):
threading.Thread(target=fun, args=a, kwargs=k).start()
def tkloop():
try:
while True:
f, a, k = q.get_nowait()
f(*a, **k)
except:
pass
root.after(100, tkloop)
root = Tk()
frame = Frame(root)
frame.pack()
Frame(root).pack( side = BOTTOM )
Button(frame, command=lambda: start_thread(button1), text="Button 1").pack( side = LEFT)
Button(frame, command=lambda: start_thread(button2), text="Button 2").pack( side = LEFT )
tkloop() # tkloop is launched here
root.mainloop()
Редактировать: двусторонняя связь : если ваши потоки должны получать информацию из основного (например, возвращаемые значения из функций tkinter), вы можете отредактировать интерфейс tkloop
, добавив очередь для возвращаемых значений , Вот пример, основанный на коде выше:
def button1():
q1 = Queue()
sleep(2)
q.put(( tkMessageBox.askokcancel, ('title', 'question'), {}, q1 ))
response = 'user said ' + 'OK' if q1.get() else 'CANCEL'
q.put(( tkMessageBox.showinfo, ('title', response), {}, None ))
# ...
def tkloop():
try:
while True:
f, a, k, qr = q.get_nowait()
r = f(*a, **k)
if qr: qr.put(r)
except:
pass
root.after(100, tkloop)