1

Finally got my first threading script "somewhat" running after going back and forth between various tutorials and online references. "Somewhat" because I am still getting some curious error during startup.

My program objective

  1. Integral up/down counter GUI constructed using tkinter. (Python 3.4.2)
  2. Spinbox as user entry point - User either enters a "target" integer or clicks on its up/down arrow.
  3. Show delayed updates on a label as the number on the label ramps up/ down to meet the "targeted" integer mentioned in (2)
  4. Updating automatically stops when the number displayed on the label equals the targeted value
  5. A "Go" button to starts the ramp up/ down after the user entered the targeted value
  6. A "Stop" button to stop the update pause the ramp up/ down anytime before reaching the targeted value

In this simplified implementation for debugging, you will see that I seemingly used the spinbox control variable unnecessarily. The actual (longer program) the control variable shall be used to trigger a validation routine for the user's input into the spinbox. And it is actually the control variable that is giving me some "curious error" during program start up

My simplified codes are as follows:

import tkinter as tk
import threading
import time

class controlPanel():
    def __init__(self, master):
        self.root = master
        self.buildWidgets()

    def buildWidgets(self):
        self.labelVar = 0
        self.ctrlVar  = tk.StringVar()
        self.ctrlVar.set(str(self.labelVar))
        self.delay = 0.5
        #+++++++++++++++++++++++++++++++++
        self.labelName  = tk.Label(self.root, text="Current Value: ", padx=3, pady=3)
        self.labelName.grid(row=0, column=0)
        self.labelValue = tk.Label(self.root, text=str(self.labelVar), padx=3, pady=3)
        self.labelValue.grid(row=0, column=1)
        #+++++++++++++++++++++++++++++++++
        self.spinboxName = tk.Label(self.root, text="Target: ", padx=3, pady=3)
        self.spinboxName.grid(row=1, column=0)
        self.spinBoxA = tk.Spinbox(self.root, from_=0, to=1000,
                                   textvariable=self.ctrlVar,
                                   width=10, justify=tk.CENTER)
        self.spinBoxA.grid(row=1, column=1)
        #+++++++++++++++++++++++++++++++++
        self.goButton   = tk.Button(self.root, text="Go", width=12,
                                    command=self.goButtonFunction,
                                    padx=3, pady=3)
        self.goButton.grid(row=2, column=1)
        #+++++++++++++++++++++++++++++++++
        self.stopButton = tk.Button(self.root, text="Stop", width=12,
                                    command=self.stopButtonFunction,
                                    padx=3, pady=3)
        self.stopButton.grid(row=2, column=0)
        #+++++++++++++++++++++++++++++++++
        #self.labelValue.update()
        #self.spinBoxA.update()
        self.root.update()

    def goButtonFunction(self):
        print('GO button clicked')
        self.flgRun = True

    def stopButtonFunction(self):
        print('STOP button clicked')
        self.flgRun = False



class controlThread(controlPanel):
    def __init__(self, master, name):
        self.root = master
        self.name = name
        controlPanel.__init__(self, self.root)

        self.flgRun     = False
        self.flgRunLock = threading.Lock()
        self.pollPeriod = 100 # polling period, in ms

        self.thread1 = threading.Thread(target = self.towardsTarget,
                                        name=self.name)
        self.thread1.daemon = False
        self.thread1.start()
        #time.sleep(5)
        self.pollFlgRun()
    #+++++++++++++++++++++++++++++++++
    def pollFlgRun(self): # polls self.flgRun every self.pollPeriod
        if self.flgRun:
            print('<< Entering pollFlgRun >>')
            time.sleep(0.01)
            self.towardsTarget()
            #self.flgRunLock.acquire() # lock thread to reset self.flgRun
            self.flgRun = False       # reset self.flgRun
            #self.flgRunLock.release() # release thread
        self.root.after(self.pollPeriod, self.pollFlgRun)
    #+++++++++++++++++++++++++++++++++    
    def towardsTarget(self):
        delay = 0.01  # delay in seconds
        time.sleep(delay)

        print('<< Entering towardsTarget >>')

        #self.flgRunLock.acquire()
        print('self.labelVar : ', str(self.labelVar))
            # Problem 1: the following reference to self.ctrlVar gave error everytime the
            # program starts. The error>> "RuntimeError: main thread is not in main loop"
            # subsequent click on controlPanel.goButton will still give the program behavior
            # specified above (i.e. no more error on subsequent clicks on the "Go" button)
            #
            # Problem 2: and when placed in a lock.acquire()/release() block, clicking the 
            # controlPanel.goButton will freeze up program 
        print('self.ctrlVar : ', self.ctrlVar.get()) 
        #self.flgRunLock.release()

        # test self.flgRun as well in case reset by controlPanel.stopButton
        while( self.flgRun and str(self.labelVar) != self.ctrlVar.get() ):
            print('WHILE loop')
            if( self.labelVar < int(self.ctrlVar.get()) ):
                self.labelVar +=1
                print('self.labelVar before sleep> ', self.labelVar)
                time.sleep(delay)
                print('self.labelVar AFTER sleep> ', self.labelVar)
                self.labelValue["text"] = str(self.labelVar)
                self.labelValue.update()
            else:
                self.labelVar -=1
                print('self.labelVar before sleep> ', self.labelVar)
                time.sleep(delay)
                print('self.labelVar AFTER sleep> ', self.labelVar)
                self.labelValue["text"] = str(self.labelVar)
                self.labelValue.update()



if __name__ == '__main__':
    root = tk.Tk()
    ctrl = controlThread(root, "X-thread")

Problem 1:

  • Within class controlThread.towardsTarget() the print statement referencing self.ctrlVar gave rise to error everytime the program starts. The error being "RuntimeError: main thread is not in main loop".
  • Subsequent click on the "Go" button (controlPanel.goButton) will still give the program behavior specified above (i.e. no more error on subsequent clicks on the "Go" button)

Problem 2:

  • when the print statement mentioned in Problem 1 is placed in a lock.acquire()/lock.release() block, clicking the "Go" button (controlPanel.goButton) will freeze up program

I've read the following two pages and the links associated

But the solutions mentioned in the two pages above did not make much sense to me as the error message "RuntimeError: main thread is not in main loop" does not appear at all if the mentioned print statement referencing self.ctrlVar was removed altogether.

My questions are

  1. What is causing this error in Problem 1?
  2. I was expecting the lock.acquire()/lock.release() block to solve the problem but it ended up freezing the program instead. How can the problem be avoided with the 'print' statement referencing self.ctrlVar in place?
Community
  • 1
  • 1

0 Answers0