首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >tkinter tkMessageBox在线程中不工作

tkinter tkMessageBox在线程中不工作
EN

Stack Overflow用户
提问于 2011-08-11 01:15:19
回答 3查看 4.5K关注 0票数 2

我有tkinter类和其中的一些函数(假设所有其他函数都存在以启动GUI)。我已经做了什么,我已经启动了一个self.function作为线程从其他self.function和线程函数出错,我想使用tkMessageBox.showerror(‘一些错误’),但这在线程函数中不起作用,我的程序被卡住了。msgbox在其他函数中工作。

代码语言:javascript
复制
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()
EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2011-08-11 02:56:14

tkinter不是线程安全的--您不能可靠地从初始化tkinter的线程以外的任何线程调用任何tkinter函数。

票数 5
EN

Stack Overflow用户

发布于 2012-07-17 02:50:27

由于我被同样的问题卡住了,并且没有找到一个适当的,解释得很好的解决方案,我想分享一下我提出的一个基本策略。

请注意,这不是使用tkinter进行线程处理的唯一方法,也不是最好的方法,但它非常简单,如果您在设计代码时没有意识到tkinter的线程不安全性,那么应该保留您的工作流。

为什么选择线程?

首先,我选择使用线程,因为像os.popensubprocess.calltime.sleep之类的阻塞操作会“冻结”图形用户界面,直到它们运行(当然这可能不是你的情况,因为线程本身出于许多原因是有用的,有时它们只是需要的)。

这是我的代码在使用线程之前的样子:

代码语言:javascript
复制
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()

Buggy线程版本

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

我认为这没问题,但在Windows上,这段代码会导致解释器无法修复地崩溃,因为tkMessageBoxes是从运行tkinter根目录的线程以外的线程调用的:

代码语言:javascript
复制
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方法和一个用于put和get请求的Queue,前者“注册一个回调函数,该函数将在给定的毫秒数后被调用”。

这样,线程就可以将元组(function, args, kwargs)放入队列,而不是调用函数,从而轻松地更改了原始代码。

这是最终的线程安全版本:

代码语言:javascript
复制
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()

编辑:双向通信:如果您的线程需要从tkloop获取信息(例如,从tkinter函数获取返回值),您可以编辑main的界面,为返回值添加一个队列。下面是一个基于上述代码的示例:

代码语言:javascript
复制
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)
票数 9
EN

Stack Overflow用户

发布于 2016-04-07 17:56:09

如果你想让你的另一个线程阻塞,直到你得到响应(例如,你想问一个问题并等待答案),你可以使用这个函数:

代码语言:javascript
复制
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]
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/7014984

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档