Python 3 - KeyboardInterrupt in background thread not detected by main thread until user hovers over GUI...












2















I've written a Python 3 TkInter-based GUI application that launches a worker thread in the background. After the worker thread has finished, it waits two seconds (this is to avoid a possible race condition), and then sends a KeyboardInterrupt to tell the main thread it can close down.



Expected behaviour: running the program launches a GUI window, prints some text to the console after which the program closes down automatically.



Actual behaviour: instead of closing automatically, it only does so after the user either hovers the mouse over the GUI window area, or presses a key on the keyboard! Apart from that the program runs without reporting any errors.



Anyone have any idea why this is happening, and how to fix this? I already tried to wrap the KeyboardInterrupt into a separate function and then call that through a timer object, but this results in the same behaviour.



I've been able to reproduce this issue on 2 different Linux machines that run Python 3.5.2. and 3.6.6 respectively.



#! /usr/bin/env python3

import os
import threading
import _thread as thread
import time
import tkinter as tk
import tkinter.scrolledtext as ScrolledText

class myGUI(tk.Frame):

# This class defines the graphical user interface

def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.root = parent
self.build_gui()

def build_gui(self):
# Build GUI
self.root.title('TEST')
self.root.option_add('*tearOff', 'FALSE')
self.grid(column=0, row=0, sticky='ew')
self.grid_columnconfigure(0, weight=1, uniform='a')

# Add text widget to display logging info
st = ScrolledText.ScrolledText(self, state='disabled')
st.configure(font='TkFixedFont')
st.grid(column=0, row=1, sticky='w', columnspan=4)

def worker():
"""Skeleton worker function, runs in separate thread (see below)"""

# Print some text to console
print("Working!")
# Wait 2 seconds to avoid race condition
time.sleep(2)
# This triggers a KeyboardInterrupt in the main thread
thread.interrupt_main()

def main():
try:
root = tk.Tk()
myGUI(root)
t1 = threading.Thread(target=worker, args=)
t1.start()
root.mainloop()
t1.join()
except KeyboardInterrupt:
# Close program if subthread issues KeyboardInterrupt
os._exit(0)

main()


(Github Gist link to the above script here)










share|improve this question



























    2















    I've written a Python 3 TkInter-based GUI application that launches a worker thread in the background. After the worker thread has finished, it waits two seconds (this is to avoid a possible race condition), and then sends a KeyboardInterrupt to tell the main thread it can close down.



    Expected behaviour: running the program launches a GUI window, prints some text to the console after which the program closes down automatically.



    Actual behaviour: instead of closing automatically, it only does so after the user either hovers the mouse over the GUI window area, or presses a key on the keyboard! Apart from that the program runs without reporting any errors.



    Anyone have any idea why this is happening, and how to fix this? I already tried to wrap the KeyboardInterrupt into a separate function and then call that through a timer object, but this results in the same behaviour.



    I've been able to reproduce this issue on 2 different Linux machines that run Python 3.5.2. and 3.6.6 respectively.



    #! /usr/bin/env python3

    import os
    import threading
    import _thread as thread
    import time
    import tkinter as tk
    import tkinter.scrolledtext as ScrolledText

    class myGUI(tk.Frame):

    # This class defines the graphical user interface

    def __init__(self, parent, *args, **kwargs):
    tk.Frame.__init__(self, parent, *args, **kwargs)
    self.root = parent
    self.build_gui()

    def build_gui(self):
    # Build GUI
    self.root.title('TEST')
    self.root.option_add('*tearOff', 'FALSE')
    self.grid(column=0, row=0, sticky='ew')
    self.grid_columnconfigure(0, weight=1, uniform='a')

    # Add text widget to display logging info
    st = ScrolledText.ScrolledText(self, state='disabled')
    st.configure(font='TkFixedFont')
    st.grid(column=0, row=1, sticky='w', columnspan=4)

    def worker():
    """Skeleton worker function, runs in separate thread (see below)"""

    # Print some text to console
    print("Working!")
    # Wait 2 seconds to avoid race condition
    time.sleep(2)
    # This triggers a KeyboardInterrupt in the main thread
    thread.interrupt_main()

    def main():
    try:
    root = tk.Tk()
    myGUI(root)
    t1 = threading.Thread(target=worker, args=)
    t1.start()
    root.mainloop()
    t1.join()
    except KeyboardInterrupt:
    # Close program if subthread issues KeyboardInterrupt
    os._exit(0)

    main()


    (Github Gist link to the above script here)










    share|improve this question

























      2












      2








      2








      I've written a Python 3 TkInter-based GUI application that launches a worker thread in the background. After the worker thread has finished, it waits two seconds (this is to avoid a possible race condition), and then sends a KeyboardInterrupt to tell the main thread it can close down.



      Expected behaviour: running the program launches a GUI window, prints some text to the console after which the program closes down automatically.



      Actual behaviour: instead of closing automatically, it only does so after the user either hovers the mouse over the GUI window area, or presses a key on the keyboard! Apart from that the program runs without reporting any errors.



      Anyone have any idea why this is happening, and how to fix this? I already tried to wrap the KeyboardInterrupt into a separate function and then call that through a timer object, but this results in the same behaviour.



      I've been able to reproduce this issue on 2 different Linux machines that run Python 3.5.2. and 3.6.6 respectively.



      #! /usr/bin/env python3

      import os
      import threading
      import _thread as thread
      import time
      import tkinter as tk
      import tkinter.scrolledtext as ScrolledText

      class myGUI(tk.Frame):

      # This class defines the graphical user interface

      def __init__(self, parent, *args, **kwargs):
      tk.Frame.__init__(self, parent, *args, **kwargs)
      self.root = parent
      self.build_gui()

      def build_gui(self):
      # Build GUI
      self.root.title('TEST')
      self.root.option_add('*tearOff', 'FALSE')
      self.grid(column=0, row=0, sticky='ew')
      self.grid_columnconfigure(0, weight=1, uniform='a')

      # Add text widget to display logging info
      st = ScrolledText.ScrolledText(self, state='disabled')
      st.configure(font='TkFixedFont')
      st.grid(column=0, row=1, sticky='w', columnspan=4)

      def worker():
      """Skeleton worker function, runs in separate thread (see below)"""

      # Print some text to console
      print("Working!")
      # Wait 2 seconds to avoid race condition
      time.sleep(2)
      # This triggers a KeyboardInterrupt in the main thread
      thread.interrupt_main()

      def main():
      try:
      root = tk.Tk()
      myGUI(root)
      t1 = threading.Thread(target=worker, args=)
      t1.start()
      root.mainloop()
      t1.join()
      except KeyboardInterrupt:
      # Close program if subthread issues KeyboardInterrupt
      os._exit(0)

      main()


      (Github Gist link to the above script here)










      share|improve this question














      I've written a Python 3 TkInter-based GUI application that launches a worker thread in the background. After the worker thread has finished, it waits two seconds (this is to avoid a possible race condition), and then sends a KeyboardInterrupt to tell the main thread it can close down.



      Expected behaviour: running the program launches a GUI window, prints some text to the console after which the program closes down automatically.



      Actual behaviour: instead of closing automatically, it only does so after the user either hovers the mouse over the GUI window area, or presses a key on the keyboard! Apart from that the program runs without reporting any errors.



      Anyone have any idea why this is happening, and how to fix this? I already tried to wrap the KeyboardInterrupt into a separate function and then call that through a timer object, but this results in the same behaviour.



      I've been able to reproduce this issue on 2 different Linux machines that run Python 3.5.2. and 3.6.6 respectively.



      #! /usr/bin/env python3

      import os
      import threading
      import _thread as thread
      import time
      import tkinter as tk
      import tkinter.scrolledtext as ScrolledText

      class myGUI(tk.Frame):

      # This class defines the graphical user interface

      def __init__(self, parent, *args, **kwargs):
      tk.Frame.__init__(self, parent, *args, **kwargs)
      self.root = parent
      self.build_gui()

      def build_gui(self):
      # Build GUI
      self.root.title('TEST')
      self.root.option_add('*tearOff', 'FALSE')
      self.grid(column=0, row=0, sticky='ew')
      self.grid_columnconfigure(0, weight=1, uniform='a')

      # Add text widget to display logging info
      st = ScrolledText.ScrolledText(self, state='disabled')
      st.configure(font='TkFixedFont')
      st.grid(column=0, row=1, sticky='w', columnspan=4)

      def worker():
      """Skeleton worker function, runs in separate thread (see below)"""

      # Print some text to console
      print("Working!")
      # Wait 2 seconds to avoid race condition
      time.sleep(2)
      # This triggers a KeyboardInterrupt in the main thread
      thread.interrupt_main()

      def main():
      try:
      root = tk.Tk()
      myGUI(root)
      t1 = threading.Thread(target=worker, args=)
      t1.start()
      root.mainloop()
      t1.join()
      except KeyboardInterrupt:
      # Close program if subthread issues KeyboardInterrupt
      os._exit(0)

      main()


      (Github Gist link to the above script here)







      python python-3.x multithreading tkinter






      share|improve this question













      share|improve this question











      share|improve this question




      share|improve this question










      asked Nov 19 '18 at 13:12









      johanjohan

      449312




      449312
























          1 Answer
          1






          active

          oldest

          votes


















          1














          root.mainloop() mainloop is blocking and pending (interceptable) signals in Python are only examined in between execution of bytecode instructions. t1.join() in your code actually never gets executed.



          Since mainloop block-waits for forwarded hardware-interrupts, for unblocking you have to provide them by e.g. hovering over the window like you saw. Only then the interpreter detects the pending KeyboardInterrupt. That's just how signal processing in Python works.



          Solving the general problem could mean finding ways to unblock blocking I/O-calls by externally injecting what's needed to unblock them, or just not using blocking calls in the first place.



          For your concrete setup, you could kill the whole process with an unhandled SIGTERM, but of course, that would be very, very ugly to do and is also unnecessary here. If you just search for a way to timeout your window, you can timeout with the tkinter.Tk.after method (shown here and here), or you get rid of mainloop and run your loop yourself (here).



          The latter could look like:



          def main():
          root = tk.Tk()
          myGUI(root)

          t1 = threading.Thread(target=worker, args=)
          t1.start()

          while True:
          try:
          root.update_idletasks()
          root.update()
          time.sleep(0.1)
          except KeyboardInterrupt:
          print('got interrupt')
          break

          t1.join()





          share|improve this answer
























          • Thanks, getting rid of mainloop as per your suggestion does the trick. Didn't know about the tkinter.TK.aftermethod, will check that out as well.

            – johan
            Nov 19 '18 at 23:35











          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%2f53375415%2fpython-3-keyboardinterrupt-in-background-thread-not-detected-by-main-thread-un%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown

























          1 Answer
          1






          active

          oldest

          votes








          1 Answer
          1






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes









          1














          root.mainloop() mainloop is blocking and pending (interceptable) signals in Python are only examined in between execution of bytecode instructions. t1.join() in your code actually never gets executed.



          Since mainloop block-waits for forwarded hardware-interrupts, for unblocking you have to provide them by e.g. hovering over the window like you saw. Only then the interpreter detects the pending KeyboardInterrupt. That's just how signal processing in Python works.



          Solving the general problem could mean finding ways to unblock blocking I/O-calls by externally injecting what's needed to unblock them, or just not using blocking calls in the first place.



          For your concrete setup, you could kill the whole process with an unhandled SIGTERM, but of course, that would be very, very ugly to do and is also unnecessary here. If you just search for a way to timeout your window, you can timeout with the tkinter.Tk.after method (shown here and here), or you get rid of mainloop and run your loop yourself (here).



          The latter could look like:



          def main():
          root = tk.Tk()
          myGUI(root)

          t1 = threading.Thread(target=worker, args=)
          t1.start()

          while True:
          try:
          root.update_idletasks()
          root.update()
          time.sleep(0.1)
          except KeyboardInterrupt:
          print('got interrupt')
          break

          t1.join()





          share|improve this answer
























          • Thanks, getting rid of mainloop as per your suggestion does the trick. Didn't know about the tkinter.TK.aftermethod, will check that out as well.

            – johan
            Nov 19 '18 at 23:35
















          1














          root.mainloop() mainloop is blocking and pending (interceptable) signals in Python are only examined in between execution of bytecode instructions. t1.join() in your code actually never gets executed.



          Since mainloop block-waits for forwarded hardware-interrupts, for unblocking you have to provide them by e.g. hovering over the window like you saw. Only then the interpreter detects the pending KeyboardInterrupt. That's just how signal processing in Python works.



          Solving the general problem could mean finding ways to unblock blocking I/O-calls by externally injecting what's needed to unblock them, or just not using blocking calls in the first place.



          For your concrete setup, you could kill the whole process with an unhandled SIGTERM, but of course, that would be very, very ugly to do and is also unnecessary here. If you just search for a way to timeout your window, you can timeout with the tkinter.Tk.after method (shown here and here), or you get rid of mainloop and run your loop yourself (here).



          The latter could look like:



          def main():
          root = tk.Tk()
          myGUI(root)

          t1 = threading.Thread(target=worker, args=)
          t1.start()

          while True:
          try:
          root.update_idletasks()
          root.update()
          time.sleep(0.1)
          except KeyboardInterrupt:
          print('got interrupt')
          break

          t1.join()





          share|improve this answer
























          • Thanks, getting rid of mainloop as per your suggestion does the trick. Didn't know about the tkinter.TK.aftermethod, will check that out as well.

            – johan
            Nov 19 '18 at 23:35














          1












          1








          1







          root.mainloop() mainloop is blocking and pending (interceptable) signals in Python are only examined in between execution of bytecode instructions. t1.join() in your code actually never gets executed.



          Since mainloop block-waits for forwarded hardware-interrupts, for unblocking you have to provide them by e.g. hovering over the window like you saw. Only then the interpreter detects the pending KeyboardInterrupt. That's just how signal processing in Python works.



          Solving the general problem could mean finding ways to unblock blocking I/O-calls by externally injecting what's needed to unblock them, or just not using blocking calls in the first place.



          For your concrete setup, you could kill the whole process with an unhandled SIGTERM, but of course, that would be very, very ugly to do and is also unnecessary here. If you just search for a way to timeout your window, you can timeout with the tkinter.Tk.after method (shown here and here), or you get rid of mainloop and run your loop yourself (here).



          The latter could look like:



          def main():
          root = tk.Tk()
          myGUI(root)

          t1 = threading.Thread(target=worker, args=)
          t1.start()

          while True:
          try:
          root.update_idletasks()
          root.update()
          time.sleep(0.1)
          except KeyboardInterrupt:
          print('got interrupt')
          break

          t1.join()





          share|improve this answer













          root.mainloop() mainloop is blocking and pending (interceptable) signals in Python are only examined in between execution of bytecode instructions. t1.join() in your code actually never gets executed.



          Since mainloop block-waits for forwarded hardware-interrupts, for unblocking you have to provide them by e.g. hovering over the window like you saw. Only then the interpreter detects the pending KeyboardInterrupt. That's just how signal processing in Python works.



          Solving the general problem could mean finding ways to unblock blocking I/O-calls by externally injecting what's needed to unblock them, or just not using blocking calls in the first place.



          For your concrete setup, you could kill the whole process with an unhandled SIGTERM, but of course, that would be very, very ugly to do and is also unnecessary here. If you just search for a way to timeout your window, you can timeout with the tkinter.Tk.after method (shown here and here), or you get rid of mainloop and run your loop yourself (here).



          The latter could look like:



          def main():
          root = tk.Tk()
          myGUI(root)

          t1 = threading.Thread(target=worker, args=)
          t1.start()

          while True:
          try:
          root.update_idletasks()
          root.update()
          time.sleep(0.1)
          except KeyboardInterrupt:
          print('got interrupt')
          break

          t1.join()






          share|improve this answer












          share|improve this answer



          share|improve this answer










          answered Nov 19 '18 at 17:38









          DarkonautDarkonaut

          3,3422822




          3,3422822













          • Thanks, getting rid of mainloop as per your suggestion does the trick. Didn't know about the tkinter.TK.aftermethod, will check that out as well.

            – johan
            Nov 19 '18 at 23:35



















          • Thanks, getting rid of mainloop as per your suggestion does the trick. Didn't know about the tkinter.TK.aftermethod, will check that out as well.

            – johan
            Nov 19 '18 at 23:35

















          Thanks, getting rid of mainloop as per your suggestion does the trick. Didn't know about the tkinter.TK.aftermethod, will check that out as well.

          – johan
          Nov 19 '18 at 23:35





          Thanks, getting rid of mainloop as per your suggestion does the trick. Didn't know about the tkinter.TK.aftermethod, will check that out as well.

          – johan
          Nov 19 '18 at 23:35




















          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%2f53375415%2fpython-3-keyboardinterrupt-in-background-thread-not-detected-by-main-thread-un%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







          這個網誌中的熱門文章

          Tangent Lines Diagram Along Smooth Curve

          Yusuf al-Mu'taman ibn Hud

          Zucchini