0

I wrote a GUI program using thread and Tkinter .I used thread since it keeps on checking for Arduino input on Port 27.

 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()

The problem with above code is that it hangs when i set application icon using self.root.wm_iconbitmap(bitmap = "icon.ico") on windows 8.1 prox64 . I am using python 2.7 with tkinter. without application icon it works .

How to sort out this problem ?

sonus21
  • 5,178
  • 2
  • 23
  • 48

2 Answers2

2

tkinter does not like to be run in any thread other than the main one. You're starting two background threads. self.foo looks OK—doesn't make any tkinter calls. But self.get_user_input calls tkinter methods pack, after and mainloop and this means you can expect two results: first, it will automatically summon Bryan Oakley, who will appear here in a puff of smoke and tell you that you cannot do this; second, it will cause undefined behaviour of your program, including sporadic or not-so-sporadic hangs and crashes. I don't know exactly how this might be interacting with wm_iconbitmap—but I know that undefined behaviour can defy all reason, and that any background thread containing tkinter calls is a ticking time-bomb at best.

The after method, which you already use, is actually a good way of avoiding the necessity of background threads in tkinter applications. The actual call to after returns immediately, so it's easy to use it from your main thread, and then the task will be scheduled to happen in the background in a safe tkinter-managed way.

Here's a way of making the main update loop of the GUI happen in the background, as an alternative to calling self.root.mainloop(). Just define this method and then call it once from the main thread---it will return (almost) immediately and then keep itself going in the background:

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

To stop it, call self.root.after_cancel( self.afterID_updateloop).

Community
  • 1
  • 1
jez
  • 14,867
  • 5
  • 37
  • 64
  • On so many occasions has the formidable @Bryan Oakley appeared and demolished my answers... Still, dude's helped me in the past. Also, calling `.after` from outside the main thread with a tiny time frame so it happens nigh on instantly is generally a good way of getting around it hanging issues, or at least I found it was. @sonu kumar could try moving any tkinter calls other than `.after` into separate methods, and call them with `.after`. – Benjamin James Drury Jan 26 '15 at 09:16
  • @BenjaminJamesDrury i couldn't understand what you are saying . Can you explain further ? – sonus21 Jan 27 '15 at 14:46
  • 1
    Sure, I'll put it in a full answer. – Benjamin James Drury Jan 27 '15 at 15:04
0

As has already been stated, calling any kind of tkinter method from a separate thread causes nothing but issues, however it is possible to sort this by using .after.

.after doesn't directly cause tkinter to change anything it is display, but queues an event to take place on the main thread that it can deal with. By calling .after with a small timescale, for example 100 milliseconds, and directing it to a method containing the tkinter calls you want to make, you can safely make a tkinter call from outside the main thread that will be executed almost instantly on the main thread.

In case I've not clearly stated my meaning, I'll put the code in below using your icon changing line as an example.

def change_icon(self):
    self.root.wm_iconbitmap(bitmap = "icon.ico")

then where you initially called to change the icon, change it to:

self.root.after(100, self.change_icon)
Benjamin James Drury
  • 2,353
  • 1
  • 14
  • 27
  • 1
    I don't think it's necessary to change icon in the background. Changing icon only has to be done once, and can be done synchronously. The apparent dependence of the crash on the icon call is probably random. Fix the main issue first, i.e. get the existing tkinter calls out of the background thread and into an `after` recursion, and then see whether the icon thing makes a difference any more—my guess is it won't. – jez Jan 27 '15 at 16:26
  • Yeah you're almost certainly right, it was just simpler for me to use the icon thing as an example as I had limited time to post the answer initially. – Benjamin James Drury Jan 27 '15 at 18:51