Python 3 - 主线程未检测到后台线程中的KeyboardInterrupt,直到用户将鼠标悬停在GUI窗口上
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python 3 - 主线程未检测到后台线程中的KeyboardInterrupt,直到用户将鼠标悬停在GUI窗口上相关的知识,希望对你有一定的参考价值。
我编写了一个基于Python 3 TkInter的GUI应用程序,它在后台启动一个工作线程。工作线程完成后,等待两秒钟(这是为了避免可能的竞争条件),然后发送一个KeyboardInterrupt来告诉它可以关闭的主线程。
预期行为:运行程序启动GUI窗口,将一些文本打印到控制台,之后程序自动关闭。
实际行为:只有在用户将鼠标悬停在GUI窗口区域上或按下键盘上的键后才会自动关闭!除此之外,程序运行时没有报告任何错误。
任何人都知道为什么会这样,以及如何解决这个问题?我已经尝试将KeyboardInterrupt包装成一个单独的函数,然后通过timer object调用它,但这会导致相同的行为。
我已经能够在运行Python 3.5.2的两台不同的Linux机器上重现这个问题。和3.6.6分别。
#! /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链接到上面的脚本here)
root.mainloop()
mainloop是阻塞的,Python中的挂起(可拦截)信号仅在执行字节码指令之间进行检查。你的代码中的t1.join()
实际上永远不会被执行。
由于mainloop
阻塞等待转发的硬件中断,因此为了解除阻塞,你必须通过例如像你看到的那样盘旋在窗户上。只有这时解释器才能检测到未决的KeyboardInterrupt
。这就是Python中信号处理的工作原理。
解决一般问题可能意味着找到解除阻塞I / O调用的方法,方法是通过外部注入解锁它们所需的内容,或者首先不使用阻塞调用。
对于你的具体设置,你可以使用未处理的SIGTERM杀死整个过程,但当然,这将是非常非常难看的,在这里也是不必要的。如果你只是想找到一种方法来超时你的窗口,你可以使用tkinter.Tk.after
方法(显示here和here)超时,或者你摆脱mainloop
并自己运行你的循环(here)。
后者可能看起来像:
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()
以上是关于Python 3 - 主线程未检测到后台线程中的KeyboardInterrupt,直到用户将鼠标悬停在GUI窗口上的主要内容,如果未能解决你的问题,请参考以下文章