Tkinter: How to use threads to preventing main event loop from “freezing”












28















I have a small GUI test with a "Start" button and a Progress bar. The desired behavior is:




  • Click Start

  • Progressbar oscillates for 5 seconds

  • Progressbar stops


The observed behavior is the "Start" button freezes for 5 seconds, then a Progressbar is displayed (no oscillation).



Here is my code so far:



class GUI:
def __init__(self, master):
self.master = master
self.test_button = Button(self.master, command=self.tb_click)
self.test_button.configure(
text="Start", background="Grey",
padx=50
)
self.test_button.pack(side=TOP)

def progress(self):
self.prog_bar = ttk.Progressbar(
self.master, orient="horizontal",
length=200, mode="indeterminate"
)
self.prog_bar.pack(side=TOP)

def tb_click(self):
self.progress()
self.prog_bar.start()
# Simulate long running process
t = threading.Thread(target=time.sleep, args=(5,))
t.start()
t.join()
self.prog_bar.stop()

root = Tk()
root.title("Test Button")
main_ui = GUI(root)
root.mainloop()


Based on the information from Bryan Oakley here, I understand that I need to use threads. I tried creating a thread, but I'm guessing that since the thread is started from within the main thread, it doesn't help.



I had the idea to place the logic portion in a different class, and instantiate the GUI from within that class, similar to the example code by A. Rodas here.



My question:



I can't figure out how to code it so that this command:



self.test_button = Button(self.master, command=self.tb_click)


calls a function that is located in the other class. Is this a Bad Thing to do or is it even possible? How would I create a 2nd class that can handle the self.tb_click? I tried following along to A. Rodas' example code which works beautifully. But I cannot figure out how to implement his solution in the case of a Button widget that triggers an action.



If I should instead handle the thread from within the single GUI class, how would one create a thread that doesn't interfere with the main thread?










share|improve this question

































    28















    I have a small GUI test with a "Start" button and a Progress bar. The desired behavior is:




    • Click Start

    • Progressbar oscillates for 5 seconds

    • Progressbar stops


    The observed behavior is the "Start" button freezes for 5 seconds, then a Progressbar is displayed (no oscillation).



    Here is my code so far:



    class GUI:
    def __init__(self, master):
    self.master = master
    self.test_button = Button(self.master, command=self.tb_click)
    self.test_button.configure(
    text="Start", background="Grey",
    padx=50
    )
    self.test_button.pack(side=TOP)

    def progress(self):
    self.prog_bar = ttk.Progressbar(
    self.master, orient="horizontal",
    length=200, mode="indeterminate"
    )
    self.prog_bar.pack(side=TOP)

    def tb_click(self):
    self.progress()
    self.prog_bar.start()
    # Simulate long running process
    t = threading.Thread(target=time.sleep, args=(5,))
    t.start()
    t.join()
    self.prog_bar.stop()

    root = Tk()
    root.title("Test Button")
    main_ui = GUI(root)
    root.mainloop()


    Based on the information from Bryan Oakley here, I understand that I need to use threads. I tried creating a thread, but I'm guessing that since the thread is started from within the main thread, it doesn't help.



    I had the idea to place the logic portion in a different class, and instantiate the GUI from within that class, similar to the example code by A. Rodas here.



    My question:



    I can't figure out how to code it so that this command:



    self.test_button = Button(self.master, command=self.tb_click)


    calls a function that is located in the other class. Is this a Bad Thing to do or is it even possible? How would I create a 2nd class that can handle the self.tb_click? I tried following along to A. Rodas' example code which works beautifully. But I cannot figure out how to implement his solution in the case of a Button widget that triggers an action.



    If I should instead handle the thread from within the single GUI class, how would one create a thread that doesn't interfere with the main thread?










    share|improve this question































      28












      28








      28


      27






      I have a small GUI test with a "Start" button and a Progress bar. The desired behavior is:




      • Click Start

      • Progressbar oscillates for 5 seconds

      • Progressbar stops


      The observed behavior is the "Start" button freezes for 5 seconds, then a Progressbar is displayed (no oscillation).



      Here is my code so far:



      class GUI:
      def __init__(self, master):
      self.master = master
      self.test_button = Button(self.master, command=self.tb_click)
      self.test_button.configure(
      text="Start", background="Grey",
      padx=50
      )
      self.test_button.pack(side=TOP)

      def progress(self):
      self.prog_bar = ttk.Progressbar(
      self.master, orient="horizontal",
      length=200, mode="indeterminate"
      )
      self.prog_bar.pack(side=TOP)

      def tb_click(self):
      self.progress()
      self.prog_bar.start()
      # Simulate long running process
      t = threading.Thread(target=time.sleep, args=(5,))
      t.start()
      t.join()
      self.prog_bar.stop()

      root = Tk()
      root.title("Test Button")
      main_ui = GUI(root)
      root.mainloop()


      Based on the information from Bryan Oakley here, I understand that I need to use threads. I tried creating a thread, but I'm guessing that since the thread is started from within the main thread, it doesn't help.



      I had the idea to place the logic portion in a different class, and instantiate the GUI from within that class, similar to the example code by A. Rodas here.



      My question:



      I can't figure out how to code it so that this command:



      self.test_button = Button(self.master, command=self.tb_click)


      calls a function that is located in the other class. Is this a Bad Thing to do or is it even possible? How would I create a 2nd class that can handle the self.tb_click? I tried following along to A. Rodas' example code which works beautifully. But I cannot figure out how to implement his solution in the case of a Button widget that triggers an action.



      If I should instead handle the thread from within the single GUI class, how would one create a thread that doesn't interfere with the main thread?










      share|improve this question
















      I have a small GUI test with a "Start" button and a Progress bar. The desired behavior is:




      • Click Start

      • Progressbar oscillates for 5 seconds

      • Progressbar stops


      The observed behavior is the "Start" button freezes for 5 seconds, then a Progressbar is displayed (no oscillation).



      Here is my code so far:



      class GUI:
      def __init__(self, master):
      self.master = master
      self.test_button = Button(self.master, command=self.tb_click)
      self.test_button.configure(
      text="Start", background="Grey",
      padx=50
      )
      self.test_button.pack(side=TOP)

      def progress(self):
      self.prog_bar = ttk.Progressbar(
      self.master, orient="horizontal",
      length=200, mode="indeterminate"
      )
      self.prog_bar.pack(side=TOP)

      def tb_click(self):
      self.progress()
      self.prog_bar.start()
      # Simulate long running process
      t = threading.Thread(target=time.sleep, args=(5,))
      t.start()
      t.join()
      self.prog_bar.stop()

      root = Tk()
      root.title("Test Button")
      main_ui = GUI(root)
      root.mainloop()


      Based on the information from Bryan Oakley here, I understand that I need to use threads. I tried creating a thread, but I'm guessing that since the thread is started from within the main thread, it doesn't help.



      I had the idea to place the logic portion in a different class, and instantiate the GUI from within that class, similar to the example code by A. Rodas here.



      My question:



      I can't figure out how to code it so that this command:



      self.test_button = Button(self.master, command=self.tb_click)


      calls a function that is located in the other class. Is this a Bad Thing to do or is it even possible? How would I create a 2nd class that can handle the self.tb_click? I tried following along to A. Rodas' example code which works beautifully. But I cannot figure out how to implement his solution in the case of a Button widget that triggers an action.



      If I should instead handle the thread from within the single GUI class, how would one create a thread that doesn't interfere with the main thread?







      python multithreading tkinter progress-bar event-loop






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited May 23 '17 at 11:33









      Community

      11




      11










      asked May 25 '13 at 1:16









      Dirty PenguinDirty Penguin

      1,42263055




      1,42263055





























          3 Answers
          3






          active

          oldest

          votes


















          44














          When you join the new thread in the main thread, it will wait until the thread finishes, so the GUI will block even though you are using multithreading.



          If you want to place the logic portion in a different class, you can subclass Thread directly, and then start a new object of this class when you press the button. The constructor of this subclass of Thread can receive a Queue object and then you will be able to communicate it with the GUI part. So my suggestion is:




          1. Create a Queue object in the main thread

          2. Create a new thread with access to that queue

          3. Check periodically the queue in the main thread


          Then you have to solve the problem of what happens if the user clicks two times the same button (it will spawn a new thread with each click), but you can fix it by disabling the start button and enabling it again after you call self.prog_bar.stop().



          import Queue

          class GUI:
          # ...

          def tb_click(self):
          self.progress()
          self.prog_bar.start()
          self.queue = Queue.Queue()
          ThreadedTask(self.queue).start()
          self.master.after(100, self.process_queue)

          def process_queue(self):
          try:
          msg = self.queue.get(0)
          # Show result of the task if needed
          self.prog_bar.stop()
          except Queue.Empty:
          self.master.after(100, self.process_queue)

          class ThreadedTask(threading.Thread):
          def __init__(self, queue):
          threading.Thread.__init__(self)
          self.queue = queue
          def run(self):
          time.sleep(5) # Simulate long running process
          self.queue.put("Task finished")





          share|improve this answer
























          • Another beautiful example. Thank you A. Rodas :) I have a follow up question: If I comment out self.master.after(100, self.process_queue) and replace it with simply self.process_queue() the behavior is the same. Is there a good reason to have the self.master.after... part?

            – Dirty Penguin
            May 25 '13 at 13:39








          • 1





            Yes, with self.master.after(100, self.process_queue) you schedule this method each 100 milliseconds, while self.process_queue() constatly executes it without any delay between each call. There is no need to do that, so after is a better solution to check the content peridically.

            – A. Rodas
            May 25 '13 at 14:28











          • Thank you once again.

            – Dirty Penguin
            May 25 '13 at 14:57






          • 1





            @citizen2077 If you want to prevent users from doing that, you can handle the WM_DELETE_PROTOCOL and only destroying the GUI if the thread is not alive.

            – A. Rodas
            Feb 4 '18 at 17:34






          • 1





            @citizen2077 Adding a handler would be the first step to define what happens if the root is closed using the window manager, but you can also use a flag to communicate the thread that it should stop its execution. Feel free to ask your question separately, since it is not strictly related with OP's question.

            – A. Rodas
            Feb 4 '18 at 17:47



















          2














          The problem is that t.join() blocks the click event, the main thread does not get back to the event loop to process repaints.
          See Why ttk Progressbar appears after process in Tkinter or TTK progress bar blocked when sending email






          share|improve this answer

































            2














            I will submit the basis for an alternate solution. It is not specific to a Tk progress bar per se, but it can certainly be implemented very easily for that.



            Here are some classes that allow you to run other tasks in the background of Tk, update the Tk controls when desired, and not lock up the gui!



            Here's class TkRepeatingTask and BackgroundTask:



            import threading

            class TkRepeatingTask():

            def __init__( self, tkRoot, taskFuncPointer, freqencyMillis ):
            self.__tk_ = tkRoot
            self.__func_ = taskFuncPointer
            self.__freq_ = freqencyMillis
            self.__isRunning_ = False

            def isRunning( self ) : return self.__isRunning_

            def start( self ) :
            self.__isRunning_ = True
            self.__onTimer()

            def stop( self ) : self.__isRunning_ = False

            def __onTimer( self ):
            if self.__isRunning_ :
            self.__func_()
            self.__tk_.after( self.__freq_, self.__onTimer )

            class BackgroundTask():

            def __init__( self, taskFuncPointer ):
            self.__taskFuncPointer_ = taskFuncPointer
            self.__workerThread_ = None
            self.__isRunning_ = False

            def taskFuncPointer( self ) : return self.__taskFuncPointer_

            def isRunning( self ) :
            return self.__isRunning_ and self.__workerThread_.isAlive()

            def start( self ):
            if not self.__isRunning_ :
            self.__isRunning_ = True
            self.__workerThread_ = self.WorkerThread( self )
            self.__workerThread_.start()

            def stop( self ) : self.__isRunning_ = False

            class WorkerThread( threading.Thread ):
            def __init__( self, bgTask ):
            threading.Thread.__init__( self )
            self.__bgTask_ = bgTask

            def run( self ):
            try :
            self.__bgTask_.taskFuncPointer()( self.__bgTask_.isRunning )
            except Exception as e: print repr(e)
            self.__bgTask_.stop()


            Here's a Tk test which demos the use of these. Just append this to the bottom of the module with those classes in it if you want to see the demo in action:



            def tkThreadingTest():

            from tkinter import Tk, Label, Button, StringVar
            from time import sleep

            class UnitTestGUI:

            def __init__( self, master ):
            self.master = master
            master.title( "Threading Test" )

            self.testButton = Button(
            self.master, text="Blocking", command=self.myLongProcess )
            self.testButton.pack()

            self.threadedButton = Button(
            self.master, text="Threaded", command=self.onThreadedClicked )
            self.threadedButton.pack()

            self.cancelButton = Button(
            self.master, text="Stop", command=self.onStopClicked )
            self.cancelButton.pack()

            self.statusLabelVar = StringVar()
            self.statusLabel = Label( master, textvariable=self.statusLabelVar )
            self.statusLabel.pack()

            self.clickMeButton = Button(
            self.master, text="Click Me", command=self.onClickMeClicked )
            self.clickMeButton.pack()

            self.clickCountLabelVar = StringVar()
            self.clickCountLabel = Label( master, textvariable=self.clickCountLabelVar )
            self.clickCountLabel.pack()

            self.threadedButton = Button(
            self.master, text="Timer", command=self.onTimerClicked )
            self.threadedButton.pack()

            self.timerCountLabelVar = StringVar()
            self.timerCountLabel = Label( master, textvariable=self.timerCountLabelVar )
            self.timerCountLabel.pack()

            self.timerCounter_=0

            self.clickCounter_=0

            self.bgTask = BackgroundTask( self.myLongProcess )

            self.timer = TkRepeatingTask( self.master, self.onTimer, 1 )

            def close( self ) :
            print "close"
            try: self.bgTask.stop()
            except: pass
            try: self.timer.stop()
            except: pass
            self.master.quit()

            def onThreadedClicked( self ):
            print "onThreadedClicked"
            try: self.bgTask.start()
            except: pass

            def onTimerClicked( self ) :
            print "onTimerClicked"
            self.timer.start()

            def onStopClicked( self ) :
            print "onStopClicked"
            try: self.bgTask.stop()
            except: pass
            try: self.timer.stop()
            except: pass

            def onClickMeClicked( self ):
            print "onClickMeClicked"
            self.clickCounter_+=1
            self.clickCountLabelVar.set( str(self.clickCounter_) )

            def onTimer( self ) :
            print "onTimer"
            self.timerCounter_+=1
            self.timerCountLabelVar.set( str(self.timerCounter_) )

            def myLongProcess( self, isRunningFunc=None ) :
            print "starting myLongProcess"
            for i in range( 1, 10 ):
            try:
            if not isRunningFunc() :
            self.onMyLongProcessUpdate( "Stopped!" )
            return
            except : pass
            self.onMyLongProcessUpdate( i )
            sleep( 1.5 ) # simulate doing work
            self.onMyLongProcessUpdate( "Done!" )

            def onMyLongProcessUpdate( self, status ) :
            print "Process Update: %s" % (status,)
            self.statusLabelVar.set( str(status) )

            root = Tk()
            gui = UnitTestGUI( root )
            root.protocol( "WM_DELETE_WINDOW", gui.close )
            root.mainloop()

            if __name__ == "__main__":
            tkThreadingTest()


            Two import points I'll stress about BackgroundTask:



            1) The function you run in the background task needs to take a function pointer it will both invoke and respect, which allows the task to be cancelled mid way through - if possible.



            2) You need to make sure the background task is stopped when you exit your application. That thread will still run even if your gui is closed if you don't address that!






            share|improve this answer


























            • Wow, I don't think you understand how the after() method works. In the accepted answer, the self.master.after(100, self.process_queue) doesn't call self.process_queue recursively. It only schedules for it to be run again in 100 ms. The second argument is just the name of the function, not a call to it—and it only does this when exception Queue.Empty was raised, meaning that the ThreadedTask hasn't put anything in queue yet, so it need to keep checking.

              – martineau
              Jan 30 '17 at 20:14













            • @martineau I hope you are right! I ran that with some slight tweaks, and it crashed due to having too many recursive calls. In other languages and libraries I've used very similar repeating timers without a problem. I would love to see that work the way it seems like it should (i.e. non recursively). I will play with that and retract my answer when I have success. Although my BackgroundTask class still works well at least in my example - I haven't tested it enough to know what it will choke on with tk being non thread safe, however, an that concerned me about it!

              – BuvinJ
              Jan 30 '17 at 21:19











            • I'm really confident about what I said. Tkinter not being thread-safe doesn't mean you can't use it in a multi-threaded application. Only that you must limit the number of threads accessing Tkinter concurrently to one (and that is usually left up to the main thread). My answer to another Tkinter question has an example of that being done.

              – martineau
              Jan 30 '17 at 22:12











            • You are quite correct! I retract my harsh comments. I've radically altered my post. I absolutely did see that recursion crash, but there must have been something else going on.

              – BuvinJ
              Jan 30 '17 at 23:39











            • I will note that I've found some issues with using my BackgroundTask class. e.g. If you create a dialog box from that thread it will appear somewhere off the screen and cause you problems!

              – BuvinJ
              Jan 30 '17 at 23:41












            Your Answer






            StackExchange.ifUsing("editor", function () {
            StackExchange.using("externalEditor", function () {
            StackExchange.using("snippets", function () {
            StackExchange.snippets.init();
            });
            });
            }, "code-snippets");

            StackExchange.ready(function() {
            var channelOptions = {
            tags: "".split(" "),
            id: "1"
            };
            initTagRenderer("".split(" "), "".split(" "), channelOptions);

            StackExchange.using("externalEditor", function() {
            // Have to fire editor after snippets, if snippets enabled
            if (StackExchange.settings.snippets.snippetsEnabled) {
            StackExchange.using("snippets", function() {
            createEditor();
            });
            }
            else {
            createEditor();
            }
            });

            function createEditor() {
            StackExchange.prepareEditor({
            heartbeatType: 'answer',
            autoActivateHeartbeat: false,
            convertImagesToLinks: true,
            noModals: true,
            showLowRepImageUploadWarning: true,
            reputationToPostImages: 10,
            bindNavPrevention: true,
            postfix: "",
            imageUploader: {
            brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
            contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
            allowUrls: true
            },
            onDemand: true,
            discardSelector: ".discard-answer"
            ,immediatelyShowMarkdownHelp:true
            });


            }
            });














            draft saved

            draft discarded


















            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f16745507%2ftkinter-how-to-use-threads-to-preventing-main-event-loop-from-freezing%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown

























            3 Answers
            3






            active

            oldest

            votes








            3 Answers
            3






            active

            oldest

            votes









            active

            oldest

            votes






            active

            oldest

            votes









            44














            When you join the new thread in the main thread, it will wait until the thread finishes, so the GUI will block even though you are using multithreading.



            If you want to place the logic portion in a different class, you can subclass Thread directly, and then start a new object of this class when you press the button. The constructor of this subclass of Thread can receive a Queue object and then you will be able to communicate it with the GUI part. So my suggestion is:




            1. Create a Queue object in the main thread

            2. Create a new thread with access to that queue

            3. Check periodically the queue in the main thread


            Then you have to solve the problem of what happens if the user clicks two times the same button (it will spawn a new thread with each click), but you can fix it by disabling the start button and enabling it again after you call self.prog_bar.stop().



            import Queue

            class GUI:
            # ...

            def tb_click(self):
            self.progress()
            self.prog_bar.start()
            self.queue = Queue.Queue()
            ThreadedTask(self.queue).start()
            self.master.after(100, self.process_queue)

            def process_queue(self):
            try:
            msg = self.queue.get(0)
            # Show result of the task if needed
            self.prog_bar.stop()
            except Queue.Empty:
            self.master.after(100, self.process_queue)

            class ThreadedTask(threading.Thread):
            def __init__(self, queue):
            threading.Thread.__init__(self)
            self.queue = queue
            def run(self):
            time.sleep(5) # Simulate long running process
            self.queue.put("Task finished")





            share|improve this answer
























            • Another beautiful example. Thank you A. Rodas :) I have a follow up question: If I comment out self.master.after(100, self.process_queue) and replace it with simply self.process_queue() the behavior is the same. Is there a good reason to have the self.master.after... part?

              – Dirty Penguin
              May 25 '13 at 13:39








            • 1





              Yes, with self.master.after(100, self.process_queue) you schedule this method each 100 milliseconds, while self.process_queue() constatly executes it without any delay between each call. There is no need to do that, so after is a better solution to check the content peridically.

              – A. Rodas
              May 25 '13 at 14:28











            • Thank you once again.

              – Dirty Penguin
              May 25 '13 at 14:57






            • 1





              @citizen2077 If you want to prevent users from doing that, you can handle the WM_DELETE_PROTOCOL and only destroying the GUI if the thread is not alive.

              – A. Rodas
              Feb 4 '18 at 17:34






            • 1





              @citizen2077 Adding a handler would be the first step to define what happens if the root is closed using the window manager, but you can also use a flag to communicate the thread that it should stop its execution. Feel free to ask your question separately, since it is not strictly related with OP's question.

              – A. Rodas
              Feb 4 '18 at 17:47
















            44














            When you join the new thread in the main thread, it will wait until the thread finishes, so the GUI will block even though you are using multithreading.



            If you want to place the logic portion in a different class, you can subclass Thread directly, and then start a new object of this class when you press the button. The constructor of this subclass of Thread can receive a Queue object and then you will be able to communicate it with the GUI part. So my suggestion is:




            1. Create a Queue object in the main thread

            2. Create a new thread with access to that queue

            3. Check periodically the queue in the main thread


            Then you have to solve the problem of what happens if the user clicks two times the same button (it will spawn a new thread with each click), but you can fix it by disabling the start button and enabling it again after you call self.prog_bar.stop().



            import Queue

            class GUI:
            # ...

            def tb_click(self):
            self.progress()
            self.prog_bar.start()
            self.queue = Queue.Queue()
            ThreadedTask(self.queue).start()
            self.master.after(100, self.process_queue)

            def process_queue(self):
            try:
            msg = self.queue.get(0)
            # Show result of the task if needed
            self.prog_bar.stop()
            except Queue.Empty:
            self.master.after(100, self.process_queue)

            class ThreadedTask(threading.Thread):
            def __init__(self, queue):
            threading.Thread.__init__(self)
            self.queue = queue
            def run(self):
            time.sleep(5) # Simulate long running process
            self.queue.put("Task finished")





            share|improve this answer
























            • Another beautiful example. Thank you A. Rodas :) I have a follow up question: If I comment out self.master.after(100, self.process_queue) and replace it with simply self.process_queue() the behavior is the same. Is there a good reason to have the self.master.after... part?

              – Dirty Penguin
              May 25 '13 at 13:39








            • 1





              Yes, with self.master.after(100, self.process_queue) you schedule this method each 100 milliseconds, while self.process_queue() constatly executes it without any delay between each call. There is no need to do that, so after is a better solution to check the content peridically.

              – A. Rodas
              May 25 '13 at 14:28











            • Thank you once again.

              – Dirty Penguin
              May 25 '13 at 14:57






            • 1





              @citizen2077 If you want to prevent users from doing that, you can handle the WM_DELETE_PROTOCOL and only destroying the GUI if the thread is not alive.

              – A. Rodas
              Feb 4 '18 at 17:34






            • 1





              @citizen2077 Adding a handler would be the first step to define what happens if the root is closed using the window manager, but you can also use a flag to communicate the thread that it should stop its execution. Feel free to ask your question separately, since it is not strictly related with OP's question.

              – A. Rodas
              Feb 4 '18 at 17:47














            44












            44








            44







            When you join the new thread in the main thread, it will wait until the thread finishes, so the GUI will block even though you are using multithreading.



            If you want to place the logic portion in a different class, you can subclass Thread directly, and then start a new object of this class when you press the button. The constructor of this subclass of Thread can receive a Queue object and then you will be able to communicate it with the GUI part. So my suggestion is:




            1. Create a Queue object in the main thread

            2. Create a new thread with access to that queue

            3. Check periodically the queue in the main thread


            Then you have to solve the problem of what happens if the user clicks two times the same button (it will spawn a new thread with each click), but you can fix it by disabling the start button and enabling it again after you call self.prog_bar.stop().



            import Queue

            class GUI:
            # ...

            def tb_click(self):
            self.progress()
            self.prog_bar.start()
            self.queue = Queue.Queue()
            ThreadedTask(self.queue).start()
            self.master.after(100, self.process_queue)

            def process_queue(self):
            try:
            msg = self.queue.get(0)
            # Show result of the task if needed
            self.prog_bar.stop()
            except Queue.Empty:
            self.master.after(100, self.process_queue)

            class ThreadedTask(threading.Thread):
            def __init__(self, queue):
            threading.Thread.__init__(self)
            self.queue = queue
            def run(self):
            time.sleep(5) # Simulate long running process
            self.queue.put("Task finished")





            share|improve this answer













            When you join the new thread in the main thread, it will wait until the thread finishes, so the GUI will block even though you are using multithreading.



            If you want to place the logic portion in a different class, you can subclass Thread directly, and then start a new object of this class when you press the button. The constructor of this subclass of Thread can receive a Queue object and then you will be able to communicate it with the GUI part. So my suggestion is:




            1. Create a Queue object in the main thread

            2. Create a new thread with access to that queue

            3. Check periodically the queue in the main thread


            Then you have to solve the problem of what happens if the user clicks two times the same button (it will spawn a new thread with each click), but you can fix it by disabling the start button and enabling it again after you call self.prog_bar.stop().



            import Queue

            class GUI:
            # ...

            def tb_click(self):
            self.progress()
            self.prog_bar.start()
            self.queue = Queue.Queue()
            ThreadedTask(self.queue).start()
            self.master.after(100, self.process_queue)

            def process_queue(self):
            try:
            msg = self.queue.get(0)
            # Show result of the task if needed
            self.prog_bar.stop()
            except Queue.Empty:
            self.master.after(100, self.process_queue)

            class ThreadedTask(threading.Thread):
            def __init__(self, queue):
            threading.Thread.__init__(self)
            self.queue = queue
            def run(self):
            time.sleep(5) # Simulate long running process
            self.queue.put("Task finished")






            share|improve this answer












            share|improve this answer



            share|improve this answer










            answered May 25 '13 at 8:17









            A. RodasA. Rodas

            15.9k64362




            15.9k64362













            • Another beautiful example. Thank you A. Rodas :) I have a follow up question: If I comment out self.master.after(100, self.process_queue) and replace it with simply self.process_queue() the behavior is the same. Is there a good reason to have the self.master.after... part?

              – Dirty Penguin
              May 25 '13 at 13:39








            • 1





              Yes, with self.master.after(100, self.process_queue) you schedule this method each 100 milliseconds, while self.process_queue() constatly executes it without any delay between each call. There is no need to do that, so after is a better solution to check the content peridically.

              – A. Rodas
              May 25 '13 at 14:28











            • Thank you once again.

              – Dirty Penguin
              May 25 '13 at 14:57






            • 1





              @citizen2077 If you want to prevent users from doing that, you can handle the WM_DELETE_PROTOCOL and only destroying the GUI if the thread is not alive.

              – A. Rodas
              Feb 4 '18 at 17:34






            • 1





              @citizen2077 Adding a handler would be the first step to define what happens if the root is closed using the window manager, but you can also use a flag to communicate the thread that it should stop its execution. Feel free to ask your question separately, since it is not strictly related with OP's question.

              – A. Rodas
              Feb 4 '18 at 17:47



















            • Another beautiful example. Thank you A. Rodas :) I have a follow up question: If I comment out self.master.after(100, self.process_queue) and replace it with simply self.process_queue() the behavior is the same. Is there a good reason to have the self.master.after... part?

              – Dirty Penguin
              May 25 '13 at 13:39








            • 1





              Yes, with self.master.after(100, self.process_queue) you schedule this method each 100 milliseconds, while self.process_queue() constatly executes it without any delay between each call. There is no need to do that, so after is a better solution to check the content peridically.

              – A. Rodas
              May 25 '13 at 14:28











            • Thank you once again.

              – Dirty Penguin
              May 25 '13 at 14:57






            • 1





              @citizen2077 If you want to prevent users from doing that, you can handle the WM_DELETE_PROTOCOL and only destroying the GUI if the thread is not alive.

              – A. Rodas
              Feb 4 '18 at 17:34






            • 1





              @citizen2077 Adding a handler would be the first step to define what happens if the root is closed using the window manager, but you can also use a flag to communicate the thread that it should stop its execution. Feel free to ask your question separately, since it is not strictly related with OP's question.

              – A. Rodas
              Feb 4 '18 at 17:47

















            Another beautiful example. Thank you A. Rodas :) I have a follow up question: If I comment out self.master.after(100, self.process_queue) and replace it with simply self.process_queue() the behavior is the same. Is there a good reason to have the self.master.after... part?

            – Dirty Penguin
            May 25 '13 at 13:39







            Another beautiful example. Thank you A. Rodas :) I have a follow up question: If I comment out self.master.after(100, self.process_queue) and replace it with simply self.process_queue() the behavior is the same. Is there a good reason to have the self.master.after... part?

            – Dirty Penguin
            May 25 '13 at 13:39






            1




            1





            Yes, with self.master.after(100, self.process_queue) you schedule this method each 100 milliseconds, while self.process_queue() constatly executes it without any delay between each call. There is no need to do that, so after is a better solution to check the content peridically.

            – A. Rodas
            May 25 '13 at 14:28





            Yes, with self.master.after(100, self.process_queue) you schedule this method each 100 milliseconds, while self.process_queue() constatly executes it without any delay between each call. There is no need to do that, so after is a better solution to check the content peridically.

            – A. Rodas
            May 25 '13 at 14:28













            Thank you once again.

            – Dirty Penguin
            May 25 '13 at 14:57





            Thank you once again.

            – Dirty Penguin
            May 25 '13 at 14:57




            1




            1





            @citizen2077 If you want to prevent users from doing that, you can handle the WM_DELETE_PROTOCOL and only destroying the GUI if the thread is not alive.

            – A. Rodas
            Feb 4 '18 at 17:34





            @citizen2077 If you want to prevent users from doing that, you can handle the WM_DELETE_PROTOCOL and only destroying the GUI if the thread is not alive.

            – A. Rodas
            Feb 4 '18 at 17:34




            1




            1





            @citizen2077 Adding a handler would be the first step to define what happens if the root is closed using the window manager, but you can also use a flag to communicate the thread that it should stop its execution. Feel free to ask your question separately, since it is not strictly related with OP's question.

            – A. Rodas
            Feb 4 '18 at 17:47





            @citizen2077 Adding a handler would be the first step to define what happens if the root is closed using the window manager, but you can also use a flag to communicate the thread that it should stop its execution. Feel free to ask your question separately, since it is not strictly related with OP's question.

            – A. Rodas
            Feb 4 '18 at 17:47













            2














            The problem is that t.join() blocks the click event, the main thread does not get back to the event loop to process repaints.
            See Why ttk Progressbar appears after process in Tkinter or TTK progress bar blocked when sending email






            share|improve this answer






























              2














              The problem is that t.join() blocks the click event, the main thread does not get back to the event loop to process repaints.
              See Why ttk Progressbar appears after process in Tkinter or TTK progress bar blocked when sending email






              share|improve this answer




























                2












                2








                2







                The problem is that t.join() blocks the click event, the main thread does not get back to the event loop to process repaints.
                See Why ttk Progressbar appears after process in Tkinter or TTK progress bar blocked when sending email






                share|improve this answer















                The problem is that t.join() blocks the click event, the main thread does not get back to the event loop to process repaints.
                See Why ttk Progressbar appears after process in Tkinter or TTK progress bar blocked when sending email







                share|improve this answer














                share|improve this answer



                share|improve this answer








                edited May 23 '17 at 12:18









                Community

                11




                11










                answered May 25 '13 at 1:32









                jmihaliczajmihalicza

                1,7381016




                1,7381016























                    2














                    I will submit the basis for an alternate solution. It is not specific to a Tk progress bar per se, but it can certainly be implemented very easily for that.



                    Here are some classes that allow you to run other tasks in the background of Tk, update the Tk controls when desired, and not lock up the gui!



                    Here's class TkRepeatingTask and BackgroundTask:



                    import threading

                    class TkRepeatingTask():

                    def __init__( self, tkRoot, taskFuncPointer, freqencyMillis ):
                    self.__tk_ = tkRoot
                    self.__func_ = taskFuncPointer
                    self.__freq_ = freqencyMillis
                    self.__isRunning_ = False

                    def isRunning( self ) : return self.__isRunning_

                    def start( self ) :
                    self.__isRunning_ = True
                    self.__onTimer()

                    def stop( self ) : self.__isRunning_ = False

                    def __onTimer( self ):
                    if self.__isRunning_ :
                    self.__func_()
                    self.__tk_.after( self.__freq_, self.__onTimer )

                    class BackgroundTask():

                    def __init__( self, taskFuncPointer ):
                    self.__taskFuncPointer_ = taskFuncPointer
                    self.__workerThread_ = None
                    self.__isRunning_ = False

                    def taskFuncPointer( self ) : return self.__taskFuncPointer_

                    def isRunning( self ) :
                    return self.__isRunning_ and self.__workerThread_.isAlive()

                    def start( self ):
                    if not self.__isRunning_ :
                    self.__isRunning_ = True
                    self.__workerThread_ = self.WorkerThread( self )
                    self.__workerThread_.start()

                    def stop( self ) : self.__isRunning_ = False

                    class WorkerThread( threading.Thread ):
                    def __init__( self, bgTask ):
                    threading.Thread.__init__( self )
                    self.__bgTask_ = bgTask

                    def run( self ):
                    try :
                    self.__bgTask_.taskFuncPointer()( self.__bgTask_.isRunning )
                    except Exception as e: print repr(e)
                    self.__bgTask_.stop()


                    Here's a Tk test which demos the use of these. Just append this to the bottom of the module with those classes in it if you want to see the demo in action:



                    def tkThreadingTest():

                    from tkinter import Tk, Label, Button, StringVar
                    from time import sleep

                    class UnitTestGUI:

                    def __init__( self, master ):
                    self.master = master
                    master.title( "Threading Test" )

                    self.testButton = Button(
                    self.master, text="Blocking", command=self.myLongProcess )
                    self.testButton.pack()

                    self.threadedButton = Button(
                    self.master, text="Threaded", command=self.onThreadedClicked )
                    self.threadedButton.pack()

                    self.cancelButton = Button(
                    self.master, text="Stop", command=self.onStopClicked )
                    self.cancelButton.pack()

                    self.statusLabelVar = StringVar()
                    self.statusLabel = Label( master, textvariable=self.statusLabelVar )
                    self.statusLabel.pack()

                    self.clickMeButton = Button(
                    self.master, text="Click Me", command=self.onClickMeClicked )
                    self.clickMeButton.pack()

                    self.clickCountLabelVar = StringVar()
                    self.clickCountLabel = Label( master, textvariable=self.clickCountLabelVar )
                    self.clickCountLabel.pack()

                    self.threadedButton = Button(
                    self.master, text="Timer", command=self.onTimerClicked )
                    self.threadedButton.pack()

                    self.timerCountLabelVar = StringVar()
                    self.timerCountLabel = Label( master, textvariable=self.timerCountLabelVar )
                    self.timerCountLabel.pack()

                    self.timerCounter_=0

                    self.clickCounter_=0

                    self.bgTask = BackgroundTask( self.myLongProcess )

                    self.timer = TkRepeatingTask( self.master, self.onTimer, 1 )

                    def close( self ) :
                    print "close"
                    try: self.bgTask.stop()
                    except: pass
                    try: self.timer.stop()
                    except: pass
                    self.master.quit()

                    def onThreadedClicked( self ):
                    print "onThreadedClicked"
                    try: self.bgTask.start()
                    except: pass

                    def onTimerClicked( self ) :
                    print "onTimerClicked"
                    self.timer.start()

                    def onStopClicked( self ) :
                    print "onStopClicked"
                    try: self.bgTask.stop()
                    except: pass
                    try: self.timer.stop()
                    except: pass

                    def onClickMeClicked( self ):
                    print "onClickMeClicked"
                    self.clickCounter_+=1
                    self.clickCountLabelVar.set( str(self.clickCounter_) )

                    def onTimer( self ) :
                    print "onTimer"
                    self.timerCounter_+=1
                    self.timerCountLabelVar.set( str(self.timerCounter_) )

                    def myLongProcess( self, isRunningFunc=None ) :
                    print "starting myLongProcess"
                    for i in range( 1, 10 ):
                    try:
                    if not isRunningFunc() :
                    self.onMyLongProcessUpdate( "Stopped!" )
                    return
                    except : pass
                    self.onMyLongProcessUpdate( i )
                    sleep( 1.5 ) # simulate doing work
                    self.onMyLongProcessUpdate( "Done!" )

                    def onMyLongProcessUpdate( self, status ) :
                    print "Process Update: %s" % (status,)
                    self.statusLabelVar.set( str(status) )

                    root = Tk()
                    gui = UnitTestGUI( root )
                    root.protocol( "WM_DELETE_WINDOW", gui.close )
                    root.mainloop()

                    if __name__ == "__main__":
                    tkThreadingTest()


                    Two import points I'll stress about BackgroundTask:



                    1) The function you run in the background task needs to take a function pointer it will both invoke and respect, which allows the task to be cancelled mid way through - if possible.



                    2) You need to make sure the background task is stopped when you exit your application. That thread will still run even if your gui is closed if you don't address that!






                    share|improve this answer


























                    • Wow, I don't think you understand how the after() method works. In the accepted answer, the self.master.after(100, self.process_queue) doesn't call self.process_queue recursively. It only schedules for it to be run again in 100 ms. The second argument is just the name of the function, not a call to it—and it only does this when exception Queue.Empty was raised, meaning that the ThreadedTask hasn't put anything in queue yet, so it need to keep checking.

                      – martineau
                      Jan 30 '17 at 20:14













                    • @martineau I hope you are right! I ran that with some slight tweaks, and it crashed due to having too many recursive calls. In other languages and libraries I've used very similar repeating timers without a problem. I would love to see that work the way it seems like it should (i.e. non recursively). I will play with that and retract my answer when I have success. Although my BackgroundTask class still works well at least in my example - I haven't tested it enough to know what it will choke on with tk being non thread safe, however, an that concerned me about it!

                      – BuvinJ
                      Jan 30 '17 at 21:19











                    • I'm really confident about what I said. Tkinter not being thread-safe doesn't mean you can't use it in a multi-threaded application. Only that you must limit the number of threads accessing Tkinter concurrently to one (and that is usually left up to the main thread). My answer to another Tkinter question has an example of that being done.

                      – martineau
                      Jan 30 '17 at 22:12











                    • You are quite correct! I retract my harsh comments. I've radically altered my post. I absolutely did see that recursion crash, but there must have been something else going on.

                      – BuvinJ
                      Jan 30 '17 at 23:39











                    • I will note that I've found some issues with using my BackgroundTask class. e.g. If you create a dialog box from that thread it will appear somewhere off the screen and cause you problems!

                      – BuvinJ
                      Jan 30 '17 at 23:41
















                    2














                    I will submit the basis for an alternate solution. It is not specific to a Tk progress bar per se, but it can certainly be implemented very easily for that.



                    Here are some classes that allow you to run other tasks in the background of Tk, update the Tk controls when desired, and not lock up the gui!



                    Here's class TkRepeatingTask and BackgroundTask:



                    import threading

                    class TkRepeatingTask():

                    def __init__( self, tkRoot, taskFuncPointer, freqencyMillis ):
                    self.__tk_ = tkRoot
                    self.__func_ = taskFuncPointer
                    self.__freq_ = freqencyMillis
                    self.__isRunning_ = False

                    def isRunning( self ) : return self.__isRunning_

                    def start( self ) :
                    self.__isRunning_ = True
                    self.__onTimer()

                    def stop( self ) : self.__isRunning_ = False

                    def __onTimer( self ):
                    if self.__isRunning_ :
                    self.__func_()
                    self.__tk_.after( self.__freq_, self.__onTimer )

                    class BackgroundTask():

                    def __init__( self, taskFuncPointer ):
                    self.__taskFuncPointer_ = taskFuncPointer
                    self.__workerThread_ = None
                    self.__isRunning_ = False

                    def taskFuncPointer( self ) : return self.__taskFuncPointer_

                    def isRunning( self ) :
                    return self.__isRunning_ and self.__workerThread_.isAlive()

                    def start( self ):
                    if not self.__isRunning_ :
                    self.__isRunning_ = True
                    self.__workerThread_ = self.WorkerThread( self )
                    self.__workerThread_.start()

                    def stop( self ) : self.__isRunning_ = False

                    class WorkerThread( threading.Thread ):
                    def __init__( self, bgTask ):
                    threading.Thread.__init__( self )
                    self.__bgTask_ = bgTask

                    def run( self ):
                    try :
                    self.__bgTask_.taskFuncPointer()( self.__bgTask_.isRunning )
                    except Exception as e: print repr(e)
                    self.__bgTask_.stop()


                    Here's a Tk test which demos the use of these. Just append this to the bottom of the module with those classes in it if you want to see the demo in action:



                    def tkThreadingTest():

                    from tkinter import Tk, Label, Button, StringVar
                    from time import sleep

                    class UnitTestGUI:

                    def __init__( self, master ):
                    self.master = master
                    master.title( "Threading Test" )

                    self.testButton = Button(
                    self.master, text="Blocking", command=self.myLongProcess )
                    self.testButton.pack()

                    self.threadedButton = Button(
                    self.master, text="Threaded", command=self.onThreadedClicked )
                    self.threadedButton.pack()

                    self.cancelButton = Button(
                    self.master, text="Stop", command=self.onStopClicked )
                    self.cancelButton.pack()

                    self.statusLabelVar = StringVar()
                    self.statusLabel = Label( master, textvariable=self.statusLabelVar )
                    self.statusLabel.pack()

                    self.clickMeButton = Button(
                    self.master, text="Click Me", command=self.onClickMeClicked )
                    self.clickMeButton.pack()

                    self.clickCountLabelVar = StringVar()
                    self.clickCountLabel = Label( master, textvariable=self.clickCountLabelVar )
                    self.clickCountLabel.pack()

                    self.threadedButton = Button(
                    self.master, text="Timer", command=self.onTimerClicked )
                    self.threadedButton.pack()

                    self.timerCountLabelVar = StringVar()
                    self.timerCountLabel = Label( master, textvariable=self.timerCountLabelVar )
                    self.timerCountLabel.pack()

                    self.timerCounter_=0

                    self.clickCounter_=0

                    self.bgTask = BackgroundTask( self.myLongProcess )

                    self.timer = TkRepeatingTask( self.master, self.onTimer, 1 )

                    def close( self ) :
                    print "close"
                    try: self.bgTask.stop()
                    except: pass
                    try: self.timer.stop()
                    except: pass
                    self.master.quit()

                    def onThreadedClicked( self ):
                    print "onThreadedClicked"
                    try: self.bgTask.start()
                    except: pass

                    def onTimerClicked( self ) :
                    print "onTimerClicked"
                    self.timer.start()

                    def onStopClicked( self ) :
                    print "onStopClicked"
                    try: self.bgTask.stop()
                    except: pass
                    try: self.timer.stop()
                    except: pass

                    def onClickMeClicked( self ):
                    print "onClickMeClicked"
                    self.clickCounter_+=1
                    self.clickCountLabelVar.set( str(self.clickCounter_) )

                    def onTimer( self ) :
                    print "onTimer"
                    self.timerCounter_+=1
                    self.timerCountLabelVar.set( str(self.timerCounter_) )

                    def myLongProcess( self, isRunningFunc=None ) :
                    print "starting myLongProcess"
                    for i in range( 1, 10 ):
                    try:
                    if not isRunningFunc() :
                    self.onMyLongProcessUpdate( "Stopped!" )
                    return
                    except : pass
                    self.onMyLongProcessUpdate( i )
                    sleep( 1.5 ) # simulate doing work
                    self.onMyLongProcessUpdate( "Done!" )

                    def onMyLongProcessUpdate( self, status ) :
                    print "Process Update: %s" % (status,)
                    self.statusLabelVar.set( str(status) )

                    root = Tk()
                    gui = UnitTestGUI( root )
                    root.protocol( "WM_DELETE_WINDOW", gui.close )
                    root.mainloop()

                    if __name__ == "__main__":
                    tkThreadingTest()


                    Two import points I'll stress about BackgroundTask:



                    1) The function you run in the background task needs to take a function pointer it will both invoke and respect, which allows the task to be cancelled mid way through - if possible.



                    2) You need to make sure the background task is stopped when you exit your application. That thread will still run even if your gui is closed if you don't address that!






                    share|improve this answer


























                    • Wow, I don't think you understand how the after() method works. In the accepted answer, the self.master.after(100, self.process_queue) doesn't call self.process_queue recursively. It only schedules for it to be run again in 100 ms. The second argument is just the name of the function, not a call to it—and it only does this when exception Queue.Empty was raised, meaning that the ThreadedTask hasn't put anything in queue yet, so it need to keep checking.

                      – martineau
                      Jan 30 '17 at 20:14













                    • @martineau I hope you are right! I ran that with some slight tweaks, and it crashed due to having too many recursive calls. In other languages and libraries I've used very similar repeating timers without a problem. I would love to see that work the way it seems like it should (i.e. non recursively). I will play with that and retract my answer when I have success. Although my BackgroundTask class still works well at least in my example - I haven't tested it enough to know what it will choke on with tk being non thread safe, however, an that concerned me about it!

                      – BuvinJ
                      Jan 30 '17 at 21:19











                    • I'm really confident about what I said. Tkinter not being thread-safe doesn't mean you can't use it in a multi-threaded application. Only that you must limit the number of threads accessing Tkinter concurrently to one (and that is usually left up to the main thread). My answer to another Tkinter question has an example of that being done.

                      – martineau
                      Jan 30 '17 at 22:12











                    • You are quite correct! I retract my harsh comments. I've radically altered my post. I absolutely did see that recursion crash, but there must have been something else going on.

                      – BuvinJ
                      Jan 30 '17 at 23:39











                    • I will note that I've found some issues with using my BackgroundTask class. e.g. If you create a dialog box from that thread it will appear somewhere off the screen and cause you problems!

                      – BuvinJ
                      Jan 30 '17 at 23:41














                    2












                    2








                    2







                    I will submit the basis for an alternate solution. It is not specific to a Tk progress bar per se, but it can certainly be implemented very easily for that.



                    Here are some classes that allow you to run other tasks in the background of Tk, update the Tk controls when desired, and not lock up the gui!



                    Here's class TkRepeatingTask and BackgroundTask:



                    import threading

                    class TkRepeatingTask():

                    def __init__( self, tkRoot, taskFuncPointer, freqencyMillis ):
                    self.__tk_ = tkRoot
                    self.__func_ = taskFuncPointer
                    self.__freq_ = freqencyMillis
                    self.__isRunning_ = False

                    def isRunning( self ) : return self.__isRunning_

                    def start( self ) :
                    self.__isRunning_ = True
                    self.__onTimer()

                    def stop( self ) : self.__isRunning_ = False

                    def __onTimer( self ):
                    if self.__isRunning_ :
                    self.__func_()
                    self.__tk_.after( self.__freq_, self.__onTimer )

                    class BackgroundTask():

                    def __init__( self, taskFuncPointer ):
                    self.__taskFuncPointer_ = taskFuncPointer
                    self.__workerThread_ = None
                    self.__isRunning_ = False

                    def taskFuncPointer( self ) : return self.__taskFuncPointer_

                    def isRunning( self ) :
                    return self.__isRunning_ and self.__workerThread_.isAlive()

                    def start( self ):
                    if not self.__isRunning_ :
                    self.__isRunning_ = True
                    self.__workerThread_ = self.WorkerThread( self )
                    self.__workerThread_.start()

                    def stop( self ) : self.__isRunning_ = False

                    class WorkerThread( threading.Thread ):
                    def __init__( self, bgTask ):
                    threading.Thread.__init__( self )
                    self.__bgTask_ = bgTask

                    def run( self ):
                    try :
                    self.__bgTask_.taskFuncPointer()( self.__bgTask_.isRunning )
                    except Exception as e: print repr(e)
                    self.__bgTask_.stop()


                    Here's a Tk test which demos the use of these. Just append this to the bottom of the module with those classes in it if you want to see the demo in action:



                    def tkThreadingTest():

                    from tkinter import Tk, Label, Button, StringVar
                    from time import sleep

                    class UnitTestGUI:

                    def __init__( self, master ):
                    self.master = master
                    master.title( "Threading Test" )

                    self.testButton = Button(
                    self.master, text="Blocking", command=self.myLongProcess )
                    self.testButton.pack()

                    self.threadedButton = Button(
                    self.master, text="Threaded", command=self.onThreadedClicked )
                    self.threadedButton.pack()

                    self.cancelButton = Button(
                    self.master, text="Stop", command=self.onStopClicked )
                    self.cancelButton.pack()

                    self.statusLabelVar = StringVar()
                    self.statusLabel = Label( master, textvariable=self.statusLabelVar )
                    self.statusLabel.pack()

                    self.clickMeButton = Button(
                    self.master, text="Click Me", command=self.onClickMeClicked )
                    self.clickMeButton.pack()

                    self.clickCountLabelVar = StringVar()
                    self.clickCountLabel = Label( master, textvariable=self.clickCountLabelVar )
                    self.clickCountLabel.pack()

                    self.threadedButton = Button(
                    self.master, text="Timer", command=self.onTimerClicked )
                    self.threadedButton.pack()

                    self.timerCountLabelVar = StringVar()
                    self.timerCountLabel = Label( master, textvariable=self.timerCountLabelVar )
                    self.timerCountLabel.pack()

                    self.timerCounter_=0

                    self.clickCounter_=0

                    self.bgTask = BackgroundTask( self.myLongProcess )

                    self.timer = TkRepeatingTask( self.master, self.onTimer, 1 )

                    def close( self ) :
                    print "close"
                    try: self.bgTask.stop()
                    except: pass
                    try: self.timer.stop()
                    except: pass
                    self.master.quit()

                    def onThreadedClicked( self ):
                    print "onThreadedClicked"
                    try: self.bgTask.start()
                    except: pass

                    def onTimerClicked( self ) :
                    print "onTimerClicked"
                    self.timer.start()

                    def onStopClicked( self ) :
                    print "onStopClicked"
                    try: self.bgTask.stop()
                    except: pass
                    try: self.timer.stop()
                    except: pass

                    def onClickMeClicked( self ):
                    print "onClickMeClicked"
                    self.clickCounter_+=1
                    self.clickCountLabelVar.set( str(self.clickCounter_) )

                    def onTimer( self ) :
                    print "onTimer"
                    self.timerCounter_+=1
                    self.timerCountLabelVar.set( str(self.timerCounter_) )

                    def myLongProcess( self, isRunningFunc=None ) :
                    print "starting myLongProcess"
                    for i in range( 1, 10 ):
                    try:
                    if not isRunningFunc() :
                    self.onMyLongProcessUpdate( "Stopped!" )
                    return
                    except : pass
                    self.onMyLongProcessUpdate( i )
                    sleep( 1.5 ) # simulate doing work
                    self.onMyLongProcessUpdate( "Done!" )

                    def onMyLongProcessUpdate( self, status ) :
                    print "Process Update: %s" % (status,)
                    self.statusLabelVar.set( str(status) )

                    root = Tk()
                    gui = UnitTestGUI( root )
                    root.protocol( "WM_DELETE_WINDOW", gui.close )
                    root.mainloop()

                    if __name__ == "__main__":
                    tkThreadingTest()


                    Two import points I'll stress about BackgroundTask:



                    1) The function you run in the background task needs to take a function pointer it will both invoke and respect, which allows the task to be cancelled mid way through - if possible.



                    2) You need to make sure the background task is stopped when you exit your application. That thread will still run even if your gui is closed if you don't address that!






                    share|improve this answer















                    I will submit the basis for an alternate solution. It is not specific to a Tk progress bar per se, but it can certainly be implemented very easily for that.



                    Here are some classes that allow you to run other tasks in the background of Tk, update the Tk controls when desired, and not lock up the gui!



                    Here's class TkRepeatingTask and BackgroundTask:



                    import threading

                    class TkRepeatingTask():

                    def __init__( self, tkRoot, taskFuncPointer, freqencyMillis ):
                    self.__tk_ = tkRoot
                    self.__func_ = taskFuncPointer
                    self.__freq_ = freqencyMillis
                    self.__isRunning_ = False

                    def isRunning( self ) : return self.__isRunning_

                    def start( self ) :
                    self.__isRunning_ = True
                    self.__onTimer()

                    def stop( self ) : self.__isRunning_ = False

                    def __onTimer( self ):
                    if self.__isRunning_ :
                    self.__func_()
                    self.__tk_.after( self.__freq_, self.__onTimer )

                    class BackgroundTask():

                    def __init__( self, taskFuncPointer ):
                    self.__taskFuncPointer_ = taskFuncPointer
                    self.__workerThread_ = None
                    self.__isRunning_ = False

                    def taskFuncPointer( self ) : return self.__taskFuncPointer_

                    def isRunning( self ) :
                    return self.__isRunning_ and self.__workerThread_.isAlive()

                    def start( self ):
                    if not self.__isRunning_ :
                    self.__isRunning_ = True
                    self.__workerThread_ = self.WorkerThread( self )
                    self.__workerThread_.start()

                    def stop( self ) : self.__isRunning_ = False

                    class WorkerThread( threading.Thread ):
                    def __init__( self, bgTask ):
                    threading.Thread.__init__( self )
                    self.__bgTask_ = bgTask

                    def run( self ):
                    try :
                    self.__bgTask_.taskFuncPointer()( self.__bgTask_.isRunning )
                    except Exception as e: print repr(e)
                    self.__bgTask_.stop()


                    Here's a Tk test which demos the use of these. Just append this to the bottom of the module with those classes in it if you want to see the demo in action:



                    def tkThreadingTest():

                    from tkinter import Tk, Label, Button, StringVar
                    from time import sleep

                    class UnitTestGUI:

                    def __init__( self, master ):
                    self.master = master
                    master.title( "Threading Test" )

                    self.testButton = Button(
                    self.master, text="Blocking", command=self.myLongProcess )
                    self.testButton.pack()

                    self.threadedButton = Button(
                    self.master, text="Threaded", command=self.onThreadedClicked )
                    self.threadedButton.pack()

                    self.cancelButton = Button(
                    self.master, text="Stop", command=self.onStopClicked )
                    self.cancelButton.pack()

                    self.statusLabelVar = StringVar()
                    self.statusLabel = Label( master, textvariable=self.statusLabelVar )
                    self.statusLabel.pack()

                    self.clickMeButton = Button(
                    self.master, text="Click Me", command=self.onClickMeClicked )
                    self.clickMeButton.pack()

                    self.clickCountLabelVar = StringVar()
                    self.clickCountLabel = Label( master, textvariable=self.clickCountLabelVar )
                    self.clickCountLabel.pack()

                    self.threadedButton = Button(
                    self.master, text="Timer", command=self.onTimerClicked )
                    self.threadedButton.pack()

                    self.timerCountLabelVar = StringVar()
                    self.timerCountLabel = Label( master, textvariable=self.timerCountLabelVar )
                    self.timerCountLabel.pack()

                    self.timerCounter_=0

                    self.clickCounter_=0

                    self.bgTask = BackgroundTask( self.myLongProcess )

                    self.timer = TkRepeatingTask( self.master, self.onTimer, 1 )

                    def close( self ) :
                    print "close"
                    try: self.bgTask.stop()
                    except: pass
                    try: self.timer.stop()
                    except: pass
                    self.master.quit()

                    def onThreadedClicked( self ):
                    print "onThreadedClicked"
                    try: self.bgTask.start()
                    except: pass

                    def onTimerClicked( self ) :
                    print "onTimerClicked"
                    self.timer.start()

                    def onStopClicked( self ) :
                    print "onStopClicked"
                    try: self.bgTask.stop()
                    except: pass
                    try: self.timer.stop()
                    except: pass

                    def onClickMeClicked( self ):
                    print "onClickMeClicked"
                    self.clickCounter_+=1
                    self.clickCountLabelVar.set( str(self.clickCounter_) )

                    def onTimer( self ) :
                    print "onTimer"
                    self.timerCounter_+=1
                    self.timerCountLabelVar.set( str(self.timerCounter_) )

                    def myLongProcess( self, isRunningFunc=None ) :
                    print "starting myLongProcess"
                    for i in range( 1, 10 ):
                    try:
                    if not isRunningFunc() :
                    self.onMyLongProcessUpdate( "Stopped!" )
                    return
                    except : pass
                    self.onMyLongProcessUpdate( i )
                    sleep( 1.5 ) # simulate doing work
                    self.onMyLongProcessUpdate( "Done!" )

                    def onMyLongProcessUpdate( self, status ) :
                    print "Process Update: %s" % (status,)
                    self.statusLabelVar.set( str(status) )

                    root = Tk()
                    gui = UnitTestGUI( root )
                    root.protocol( "WM_DELETE_WINDOW", gui.close )
                    root.mainloop()

                    if __name__ == "__main__":
                    tkThreadingTest()


                    Two import points I'll stress about BackgroundTask:



                    1) The function you run in the background task needs to take a function pointer it will both invoke and respect, which allows the task to be cancelled mid way through - if possible.



                    2) You need to make sure the background task is stopped when you exit your application. That thread will still run even if your gui is closed if you don't address that!







                    share|improve this answer














                    share|improve this answer



                    share|improve this answer








                    edited Jan 30 '17 at 23:37

























                    answered Jan 29 '17 at 22:29









                    BuvinJBuvinJ

                    4,11523350




                    4,11523350













                    • Wow, I don't think you understand how the after() method works. In the accepted answer, the self.master.after(100, self.process_queue) doesn't call self.process_queue recursively. It only schedules for it to be run again in 100 ms. The second argument is just the name of the function, not a call to it—and it only does this when exception Queue.Empty was raised, meaning that the ThreadedTask hasn't put anything in queue yet, so it need to keep checking.

                      – martineau
                      Jan 30 '17 at 20:14













                    • @martineau I hope you are right! I ran that with some slight tweaks, and it crashed due to having too many recursive calls. In other languages and libraries I've used very similar repeating timers without a problem. I would love to see that work the way it seems like it should (i.e. non recursively). I will play with that and retract my answer when I have success. Although my BackgroundTask class still works well at least in my example - I haven't tested it enough to know what it will choke on with tk being non thread safe, however, an that concerned me about it!

                      – BuvinJ
                      Jan 30 '17 at 21:19











                    • I'm really confident about what I said. Tkinter not being thread-safe doesn't mean you can't use it in a multi-threaded application. Only that you must limit the number of threads accessing Tkinter concurrently to one (and that is usually left up to the main thread). My answer to another Tkinter question has an example of that being done.

                      – martineau
                      Jan 30 '17 at 22:12











                    • You are quite correct! I retract my harsh comments. I've radically altered my post. I absolutely did see that recursion crash, but there must have been something else going on.

                      – BuvinJ
                      Jan 30 '17 at 23:39











                    • I will note that I've found some issues with using my BackgroundTask class. e.g. If you create a dialog box from that thread it will appear somewhere off the screen and cause you problems!

                      – BuvinJ
                      Jan 30 '17 at 23:41



















                    • Wow, I don't think you understand how the after() method works. In the accepted answer, the self.master.after(100, self.process_queue) doesn't call self.process_queue recursively. It only schedules for it to be run again in 100 ms. The second argument is just the name of the function, not a call to it—and it only does this when exception Queue.Empty was raised, meaning that the ThreadedTask hasn't put anything in queue yet, so it need to keep checking.

                      – martineau
                      Jan 30 '17 at 20:14













                    • @martineau I hope you are right! I ran that with some slight tweaks, and it crashed due to having too many recursive calls. In other languages and libraries I've used very similar repeating timers without a problem. I would love to see that work the way it seems like it should (i.e. non recursively). I will play with that and retract my answer when I have success. Although my BackgroundTask class still works well at least in my example - I haven't tested it enough to know what it will choke on with tk being non thread safe, however, an that concerned me about it!

                      – BuvinJ
                      Jan 30 '17 at 21:19











                    • I'm really confident about what I said. Tkinter not being thread-safe doesn't mean you can't use it in a multi-threaded application. Only that you must limit the number of threads accessing Tkinter concurrently to one (and that is usually left up to the main thread). My answer to another Tkinter question has an example of that being done.

                      – martineau
                      Jan 30 '17 at 22:12











                    • You are quite correct! I retract my harsh comments. I've radically altered my post. I absolutely did see that recursion crash, but there must have been something else going on.

                      – BuvinJ
                      Jan 30 '17 at 23:39











                    • I will note that I've found some issues with using my BackgroundTask class. e.g. If you create a dialog box from that thread it will appear somewhere off the screen and cause you problems!

                      – BuvinJ
                      Jan 30 '17 at 23:41

















                    Wow, I don't think you understand how the after() method works. In the accepted answer, the self.master.after(100, self.process_queue) doesn't call self.process_queue recursively. It only schedules for it to be run again in 100 ms. The second argument is just the name of the function, not a call to it—and it only does this when exception Queue.Empty was raised, meaning that the ThreadedTask hasn't put anything in queue yet, so it need to keep checking.

                    – martineau
                    Jan 30 '17 at 20:14







                    Wow, I don't think you understand how the after() method works. In the accepted answer, the self.master.after(100, self.process_queue) doesn't call self.process_queue recursively. It only schedules for it to be run again in 100 ms. The second argument is just the name of the function, not a call to it—and it only does this when exception Queue.Empty was raised, meaning that the ThreadedTask hasn't put anything in queue yet, so it need to keep checking.

                    – martineau
                    Jan 30 '17 at 20:14















                    @martineau I hope you are right! I ran that with some slight tweaks, and it crashed due to having too many recursive calls. In other languages and libraries I've used very similar repeating timers without a problem. I would love to see that work the way it seems like it should (i.e. non recursively). I will play with that and retract my answer when I have success. Although my BackgroundTask class still works well at least in my example - I haven't tested it enough to know what it will choke on with tk being non thread safe, however, an that concerned me about it!

                    – BuvinJ
                    Jan 30 '17 at 21:19





                    @martineau I hope you are right! I ran that with some slight tweaks, and it crashed due to having too many recursive calls. In other languages and libraries I've used very similar repeating timers without a problem. I would love to see that work the way it seems like it should (i.e. non recursively). I will play with that and retract my answer when I have success. Although my BackgroundTask class still works well at least in my example - I haven't tested it enough to know what it will choke on with tk being non thread safe, however, an that concerned me about it!

                    – BuvinJ
                    Jan 30 '17 at 21:19













                    I'm really confident about what I said. Tkinter not being thread-safe doesn't mean you can't use it in a multi-threaded application. Only that you must limit the number of threads accessing Tkinter concurrently to one (and that is usually left up to the main thread). My answer to another Tkinter question has an example of that being done.

                    – martineau
                    Jan 30 '17 at 22:12





                    I'm really confident about what I said. Tkinter not being thread-safe doesn't mean you can't use it in a multi-threaded application. Only that you must limit the number of threads accessing Tkinter concurrently to one (and that is usually left up to the main thread). My answer to another Tkinter question has an example of that being done.

                    – martineau
                    Jan 30 '17 at 22:12













                    You are quite correct! I retract my harsh comments. I've radically altered my post. I absolutely did see that recursion crash, but there must have been something else going on.

                    – BuvinJ
                    Jan 30 '17 at 23:39





                    You are quite correct! I retract my harsh comments. I've radically altered my post. I absolutely did see that recursion crash, but there must have been something else going on.

                    – BuvinJ
                    Jan 30 '17 at 23:39













                    I will note that I've found some issues with using my BackgroundTask class. e.g. If you create a dialog box from that thread it will appear somewhere off the screen and cause you problems!

                    – BuvinJ
                    Jan 30 '17 at 23:41





                    I will note that I've found some issues with using my BackgroundTask class. e.g. If you create a dialog box from that thread it will appear somewhere off the screen and cause you problems!

                    – BuvinJ
                    Jan 30 '17 at 23:41


















                    draft saved

                    draft discarded




















































                    Thanks for contributing an answer to Stack Overflow!


                    • Please be sure to answer the question. Provide details and share your research!

                    But avoid



                    • Asking for help, clarification, or responding to other answers.

                    • Making statements based on opinion; back them up with references or personal experience.


                    To learn more, see our tips on writing great answers.




                    draft saved


                    draft discarded














                    StackExchange.ready(
                    function () {
                    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f16745507%2ftkinter-how-to-use-threads-to-preventing-main-event-loop-from-freezing%23new-answer', 'question_page');
                    }
                    );

                    Post as a guest















                    Required, but never shown





















































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown

































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown







                    這個網誌中的熱門文章

                    Xamarin.form Move up view when keyboard appear

                    Post-Redirect-Get with Spring WebFlux and Thymeleaf

                    Anylogic : not able to use stopDelay()