1

I'm using Tkinter to make an interactive Data Science App. I'm kinda new using Tkinter, so sorry if I don't use the correct technical language. There's a process where there´s heavy image processing. I also need a window which shows a progress bar of how the image processing is going. In particular, the image processing makes N things, and the window should show the progress bar, and text with n/N, percentage, and time passed. The process starts with a start button. It works at first, until some point where it just freezes. This is how my GUI class looks:

class progress_bar:
   def __init__(self, parent, queue):

       self.queue = queue

       self.parent = parent
       self.parent.geometry("300x100")
       self.frame = tk.Frame(parent)
       self.frame.pack()

       self.progress = ttk.Progressbar(self.frame, orient = tk.HORIZONTAL,
             length = 250, mode = 'determinate')
       self.startButton = tk.Button(self.frame, text="Start", command=self.update_bar)
       self.label = tk.Label(self.frame, text="")

       self.progress.pack(pady=15)
       self.label.pack(pady=0)
       self.startButton.pack(pady=5)
       self.parent.eval('tk::PlaceWindow . center')
       self.startButton.focus()
    
   def update_bar(self):
       self.queue.put(True)
       time.sleep(4)
       while True:
           if not self.queue.empty():
               returns = self.queue.get()
               self.progress['value'] = int(100*(returns[0]/returns[1]))
               self.label.config(text = f"{returns[0]}/{returns[1]} ({int(100*(returns[0]/returns[1]))}%)"+"   Time: "+
                                    str(int(returns[2]/3600))+" hrs "+ str(int(int(returns[2]/60)%60))+" min "+str(int(returns[2]%60))+" s")
               self.frame.update_idletasks()
               if returns[0] == returns[1]:
                   break
       time.sleep(0.8)
       self.parent.destroy()

Because of the heavy image processing, I create a new proccess with multiprocessing.Process and the stats of that process are brought back for the GUI through a Queue. In fact, that Queue is given to the progress_bar object when created and brought from the child process (image processing) to update the progress bar window. To run the code I have something like this:

if __name__ == '__main__':
   ... some stuff ...

   progress_queue = Queue()
   process = Process(target=image_processing_function, args=its_args)
   process.start()

   root = tk.Tk()
   window = progress_bar(root, progress_queue)
   root.mainloop()
   

It works fine at first, but reaching 2% of progress, it freezes. The image processing running on the other core keeps running perfectly, it's just tkinter that freezes.

The image processing function is actually on another python script called "mark_finder.py", so I just import it in the main script with "import mark_finder.py as mf" and then just use the function (called "create_path_df") in the multiprocessing.Process like "Process(target=mf.create_path_df, args=its_args). This function is recursive and does the following:

event_cnt = 0
exe_time = 0
first_call = True

def create_path_df(source_path, df_paths, progress_queue, verbose_events):
   global first_call
   while first_call and progress_queue.empty():
      pass

   if first_call and progress_queue.get():
      first_call =False

   global event_cnt
   global exe_time

   .... stuff...
   
   # base case:
   ... stuff ...
   progress_queue.put([event_cnt, verbose_events, exe_time])

   ... stuff...

   # recursive case:
   ... stuff ...

   somewhere here I make my recursive call

   ... more stuff ...

It works perfectly for a while, but then Tkinter just freezes, and the second process keeps running perfectly. Looking at the task manager, the tkinter script uses between 5% and 7% of the CPU and the image processing process uses between 10% and 15% of the CPU. My CPU is an AMD Ryzen 7 3700X 8-Core Processor.

Flair
  • 2,609
  • 1
  • 29
  • 41
wernergh9
  • 21
  • 4
  • Its hard to predict without a reproducible code. You may need to debug this. You may want to see the behavior by adding print statements before you are adding items to the queue and after you are getting items from the queue. One more observation the `time.sleep(0.8)` is not inside the while loop. And yeh.. you don't have use this if not `self.queue.empty():` and `time.sleep` the `queue.get()` will block until an item is available. – Meritor Feb 02 '22 at 12:03
  • or provide reproducible code so that we can try it out – Meritor Feb 02 '22 at 12:03
  • Thanks for your response. I managed to fix the problem. The `time.sleep(0.8)` is just for my stupid satisfaction: avoid the window from closing inmediatly after it hits 100% xd. The problem was that you need to be constantly updating the window if there's a parallel process running. So in the while loop of the `update_bar` method, I had to add `self.parent.update()`, right before the `if` statement: `if not self.queue.empty():`. It would be interesting to know exactly why this works, if someone knows the answer. PD: thanks for the tip on the `queue.get()`, I'll try to implement that way. – wernergh9 Feb 04 '22 at 00:52
  • That's good that you got that working.. however, be careful while calling `.update()` did you check `update_idletasks()` https://stackoverflow.com/questions/29158811/whats-the-difference-between-update-and-update-idletasks – Meritor Feb 07 '22 at 10:12
  • Nice, I just read the link on the update methods. So, the first thing I tried to put in practice before actually studying if I should use `.update()` or `update_idletasks()`, was: "The circumstances when you should use update over update_idletasks? Almost never. In all honesty, my pragmatic answer is "never call update unless calling update_idletasks doesn't do enough"." `update_idletasks()` didn't work, so Im gonna stick with `update()`. – wernergh9 Feb 10 '22 at 13:30

0 Answers0