tkinter tkMessageBox无法在线程中工作

Aam*_*nan 2 multithreading tkinter

我有tkinter类和其中的一些函数,(假设所有其他函数都存在以启动GUI).我做了什么我已经启动了一个self.function作为来自其他self.function和线程函数的线程在出错时我想使用tkMessageBox.showerror('Some Error')但这不适用于线程函数和我的程序得到了卡住.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()
Run Code Online (Sandbox Code Playgroud)

etu*_*rdu 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()
Run Code Online (Sandbox Code Playgroud)

Buggy线程版

然后我将按钮调用的命令转换为线程.这样,GUI就不会冻结.

我认为这没关系,但是在Windows上,这段代码导致解释器无法挽回地崩溃,因为tkMessageBox除了tkinter的root运行之外的线程调用了es:

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()
Run Code Online (Sandbox Code Playgroud)

线程安全版

当我发现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()
Run Code Online (Sandbox Code Playgroud)

编辑:双向通信:如果您的线程需要从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)
Run Code Online (Sandbox Code Playgroud)


Bry*_*ley 5

tkinter不是线程安全的 - 你不能从你初始化tkinter的线程之外的任何线程中可靠地调用任何tkinter函数.