我有tkinter类和其中的一些函数,(假设所有其他函数都存在以启动GUI)。我做了什么我已经开始一个self.function作为一个线程来自其他self.function和线程函数出错我想使用tkMessageBox.showerror('一些错误')但这不适用于线程函数和我的程序得到卡住。 msgbox正在使用其他功能。
import threading
from Tkinter import *
import Pmw
import tkMessageBox
class tkinter_ui:
def __init__(self, title=''):
... assume all functions are present ...
def login(self, username, password)
if password == "":
tkMessageBox.showerror('Login Error', 'password required') # but on this msg box program become unresponsive why???
def initiateLogin(self)
tkMessageBox.showinfo('Thread', 'Started') #you see this msg box works
self.t = threading.Timer(1, self.login)
self.t.start()
答案 0 :(得分:9)
由于我遇到了同样的问题并且找不到合适的,解释得很好的解决方案,我想分享一下我提出的基本策略。
请注意,这不是使用tkinter进行线程处理的唯一方法,也不是最好的方法,但它非常简单,如果您设计代码而不知道tkinter的线程不安全,则应保留您的工作流程。
首先,我选择使用线程看到os.popen
,subprocess.call
,time.sleep
之类的阻止操作会“冻结”GUI直到它们运行(当然这可能不是你的情况,因为线程由于很多原因而自己有用,有时只需要它们。)
这是我的代码在使用线程之前的样子:
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()
然后我将按钮调用的命令转换为线程。这样,GUI就不会冻结。
我认为没问题,但是在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()
编辑:双向通信:如果您的线程需要从main获取信息(例如,从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)
答案 1 :(得分:5)
tkinter不是线程安全的 - 除了初始化tkinter之外的任何线程,你都无法可靠地调用任何tkinter函数。
答案 2 :(得分:0)
如果您希望其他线程阻止,直到您收到回复(例如:您想提问并等待答案),您可以使用此功能:
def runInGuiThreadAndReturnValue(self, fun, *args, **kwargs):
def runInGui(fun, ret, args, kwargs):
ret.append(fun( *args, **kwargs))
ret = []
sleeptime = kwargs.pop('sleeptime', 0.5)
self.after(0, runInGui, fun, ret, args, kwargs)
while not ret:
time.sleep(sleeptime)
return ret[0]