Python:Tkinter中的更改应用程序图标挂起

Python:Tkinter中的更改应用程序图标挂起,python,multithreading,python-2.7,tkinter,Python,Multithreading,Python 2.7,Tkinter,我使用thread和Tkinter编写了一个GUI程序。我使用thread,因为它一直在检查端口27上的Arduino输入 def main(): t = Test() t.go() try: join_threads(t.threads) except KeyboardInterrupt: print "\nKeyboardInterrupt catched." print "Terminate main thr

我使用thread和Tkinter编写了一个GUI程序。我使用thread,因为它一直在检查端口27上的Arduino输入

 def main():
    t = Test()
    t.go()
    try:
        join_threads(t.threads)
    except KeyboardInterrupt:
        print "\nKeyboardInterrupt catched."
        print "Terminate main thread."
        print "If only daemonic threads are left, terminate whole program."

class Test(object):

    def __init__(self):
        self.running = True
        self.threads = []
        self.root=Tk()
        self.Rval = IntVar()
        self.Rval.set(2)
        self.root.title("RFID EM LOCK CONTROLLER")
        self.variable=StringVar()
        self.variable2=StringVar()
        self.var2=StringVar()
        self.var3=StringVar()
        self.i=0
        self.root.resizable(0,0)
        self.your_label=Label(self.root,textvariable=self.variable,width=40,height=5,bg="Black",fg="Green")
        self.lframe = Frame(self.root,width=300,height=200,padx=0)
        self.lframe.pack()
        self.root.wm_iconbitmap(bitmap = "icon.ico")

    def foo(self):
        ser=serial.Serial("COM27",9600)
        while(self.running):
            self.var2= ser.readline()
            v = self.var2[0:8];
            print v
            if self.Isexist(v):
                ser.write('A')
                self.var2="Valid Card\n"+"Card Number: "+v; 
            else:
                ser.write('B')
                self.var2="InValid Card\n"+"Card Number: "+v;
    def grid(self):
        self.your_label.pack()

    def update_label(self):
        self.i=self.i+1
        self.variable.set(str(self.var2))
        self.variable2.set(str(self.var2))
        self.root.after(20,self.update_label)                       
    def get_user_input(self):
        self.grid()
        self.root.after(20,self.update_label)
        self.root.mainloop()

    def go(self):
        t1 = threading.Thread(target=self.foo)
        t2 = threading.Thread(target=self.get_user_input)
        # Make threads daemonic, i.e. terminate them when main thread
        # terminates. From: http://stackoverflow.com/a/3788243/145400
        t1.daemon = True
        t2.daemon = True
        t1.start()
        t2.start()
        self.threads.append(t1)
        self.threads.append(t2)


def join_threads(threads):
    """
    Join threads in interruptable fashion.
    From http://stackoverflow.com/a/9790882/145400
    """
    for t in threads:
        while t.isAlive():
            t.join(5)

if __name__ == "__main__":
    main()
上面代码的问题是,当我在windows 8.1 prox64上使用
self.root.wm_iconbitmap(bitmap=“icon.ico”)
设置应用程序图标时,它会挂起。我将Python2.7与tkinter一起使用。如果没有应用程序图标,它可以工作

如何解决这个问题?

tkinter
不喜欢在主线程以外的任何线程中运行。您正在启动两个后台线程
self.foo
看起来很好,不进行任何
tkinter
调用。但是
self.get\u user\u input
调用
tkinter
方法
pack
mainloop
,这意味着您可以预期两个结果:首先,它会自动调用,他会冒着一股烟出现在这里,告诉您不能这样做;其次,它将导致程序的未定义行为,包括偶发或不太偶发的挂起和崩溃。我不知道这可能是如何与
wm_iconbitmap
交互的,但我知道未定义的行为可以无视一切原因,任何包含
tkinter
调用的后台线程充其量都是一颗滴答作响的定时炸弹

您已经使用的
after
方法实际上是避免
tkinter
应用程序中需要后台线程的好方法。在
之后对
的实际调用会立即返回,因此可以从主线程轻松使用它,然后任务将以安全的
tkinter
管理方式安排在后台执行

下面是一种使GUI的主更新循环发生在后台的方法,作为调用
self.root.mainloop()
的替代方法。只需定义此方法,然后从主线程调用它一次——它将立即(几乎)返回,然后在后台继续运行:

def background_updateloop( self ):
    self.root.update()
    self.afterID_updateloop = self.root.after( 100, self.background_updateloop )

要停止它,请调用
self.root.after\u cancel(self.afterID\u updatelop)
正如前面所述,从单独的线程调用任何类型的tkinter方法都只会导致问题,但是可以使用
.after
对其进行排序

.after
不会直接导致tkinter更改它显示的任何内容,而是将事件排队,以便在它可以处理的主线程上发生。通过使用较小的时间刻度(例如100毫秒)调用
后,将其定向到包含要进行的tkinter调用的方法,您可以安全地从主线程外部进行tkinter调用,该调用将在主线程上几乎立即执行

如果我没有明确说明我的意思,我将以你的图标更改行为例,将代码放在下面

def change_icon(self):
    self.root.wm_iconbitmap(bitmap = "icon.ico")
然后,在最初调用以更改图标的位置,将其更改为:

self.root.after(100, self.change_icon)

在这么多的场合,令人生畏的@Bryan Oakley出现并摧毁了我的答案。。。不过,这家伙过去帮过我。另外,从主线程外部调用
.after
,时间范围很小,因此几乎可以立即调用,这通常是解决悬而未决问题的一个好方法,至少我发现是这样@sonu kumar可以尝试将除
.after
之外的任何tkinter调用移动到单独的方法中,并使用
调用它们。after
@BenjaminJamesDrury我不明白你在说什么。你能进一步解释一下吗?当然,我会把它写在一个完整的答案里。我认为没有必要改变背景中的图标。更改图标只需执行一次,并且可以同步执行。崩溃对图标调用的明显依赖可能是随机的。首先修复主要问题,即将现有的tkinter调用从后台线程中取出,并在
递归后放入一个
,然后看看图标是否会产生任何影响我猜不会。是的,你几乎肯定是对的,对于我来说,使用图标作为例子更简单,因为我最初发布答案的时间有限。