Python 3 - KeyboardInterrupt in background thread not detected by main thread until user hovers over GUI...
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
add a comment |
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
add a comment |
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
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
python python-3.x multithreading tkinter
asked Nov 19 '18 at 13:12
johanjohan
449312
449312
add a comment |
add a comment |
1 Answer
1
active
oldest
votes
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()
Thanks, getting rid ofmainloop
as per your suggestion does the trick. Didn't know about thetkinter.TK.after
method, will check that out as well.
– johan
Nov 19 '18 at 23:35
add a comment |
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
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
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()
Thanks, getting rid ofmainloop
as per your suggestion does the trick. Didn't know about thetkinter.TK.after
method, will check that out as well.
– johan
Nov 19 '18 at 23:35
add a comment |
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()
Thanks, getting rid ofmainloop
as per your suggestion does the trick. Didn't know about thetkinter.TK.after
method, will check that out as well.
– johan
Nov 19 '18 at 23:35
add a comment |
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()
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()
answered Nov 19 '18 at 17:38
DarkonautDarkonaut
3,3422822
3,3422822
Thanks, getting rid ofmainloop
as per your suggestion does the trick. Didn't know about thetkinter.TK.after
method, will check that out as well.
– johan
Nov 19 '18 at 23:35
add a comment |
Thanks, getting rid ofmainloop
as per your suggestion does the trick. Didn't know about thetkinter.TK.after
method, 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.after
method, 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.after
method, will check that out as well.– johan
Nov 19 '18 at 23:35
add a comment |
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.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
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