关闭我的 tkinter 串行应用程序,给我一个例外

Closing my tkinter serial app, gives me an exception

我有这个程序可以从串口抓取数据并将它们显示在 tkinter 框架上。

这是代码:

import tkinter as tk
import tkinter.ttk as ttk
import serial.tools.list_ports
from tkinter import scrolledtext 
#new stuff from vid
import time
import serial
import threading
import continuous_threading


#to be used on our canvas
HEIGHT = 700
WIDTH = 800

#hardcoded baud rate
baudRate = 9600



ser = serial.Serial('COM16', baudRate)
val1 = 0
index = []
def readSerial():
    global val1
    ser_bytes = ser.readline()
    ser_bytes = ser_bytes.decode("utf-8")
    val1 = ser_bytes
    scrollbar.insert("end", val1)
    
t1 = continuous_threading.PeriodicThread(0.1, readSerial)
#----------------------------------------------------------------------

# --- functions ---

#the following two functtions are for the seria port selection, on frame 1
def serial_ports():    
    return serial.tools.list_ports.comports()

def on_select(event=None):

    global COMPort
    COMPort = cb.get()
    print(COMPort)



# --- functions ---


#--------------------------------------------------------------------------------------------------------------
# --- main ---
root = tk.Tk() #here we create our tkinter window
root.title("Sensor Interface")

#we use canvas as a placeholder, to get our initial screen size (we have defined HEIGHT and WIDTH)
canvas = tk.Canvas(root, height=HEIGHT, width=WIDTH)
canvas.pack()

#we use frames to organize all the widgets in the screen
# --- frame 1 ---
frame1 = tk.Frame(root)
frame1.place(relx=0, rely=0.05, relheight=0.03, relwidth=1, anchor='nw') #we use relheight and relwidth to fill whatever the parent is - in this case- root

label0 = tk.Label(frame1, text="Select the COM port that the device is plugged in: ")
label0.config(font=("TkDefaultFont", 8))
label0.place(relx = 0.1, rely=0.3, relwidth=0.3, relheight=0.5)


cb = ttk.Combobox(frame1, values=serial_ports())
cb.place(relx=0.5, rely=0.5, anchor='center')
# assign function to cmbobox
cb.bind('<<ComboboxSelected>>', on_select)
# --- frame 1 ---




# --- frame 2 ---
frame2 = tk.Frame(root, bg='#80c1ff') #remove color later
frame2.place(relx=0, rely=0.1, relheight=1, relwidth=1, anchor='nw')

# make a scrollbar
scrollbar = scrolledtext.ScrolledText(frame2)
scrollbar.place(relx=0, rely=0, relheight=1, relwidth=1, anchor='nw')
# --- frame 2 ---
#--------------------------------------------------------------------------------------------------------
t1.start() 

root.mainloop() #here we run our app

当我终止生成的 GUI 时,我在终端上收到此异常:

Exception in thread Thread-1:
Fatal Python error: could not acquire lock for <_io.BufferedWriter name='<stderr
>'> at interpreter shutdown, possibly due to daemon threads
Python runtime state: finalizing (tstate=005C8F38)

Thread 0x00001a30 (most recent call first):
  File "C:\Users\User1\AppData\Local\Programs\Python\Python38-32\lib\threading.p
y", line 1202 in invoke_excepthook
  File "C:\Users\User1\AppData\Local\Programs\Python\Python38-32\lib\threading.p
y", line 934 in _bootstrap_inner
  File "C:\Users\User1\AppData\Local\Programs\Python\Python38-32\lib\threading.p
y", line 890 in _bootstrap

Current thread 0x00001918 (most recent call first):
<no Python frame>

当我使用 control+C 从终端终止它时,我得到:

Traceback (most recent call last):
  File "mySerial.py", line 110, in <module>
    root.mainloop() #here we run our app
  File "C:\Users\User1\AppData\Local\Programs\Python\Python38-32\lib\tkinter\__i
nit__.py", line 1420, in mainloop
    self.tk.mainloop(n)
KeyboardInterrupt
Exception in thread Thread-1:
Fatal Python error: could not acquire lock for <_io.BufferedWriter name='<stderr
>'> at interpreter shutdown, possibly due to daemon threads
Python runtime state: finalizing (tstate=00578F38)

Thread 0x00001ad0 (most recent call first):
  File "C:\Users\User1\AppData\Local\Programs\Python\Python38-32\lib\threading.p
y", line 1202 in invoke_excepthook
  File "C:\Users\User1\AppData\Local\Programs\Python\Python38-32\lib\threading.p
y", line 934 in _bootstrap_inner
  File "C:\Users\User1\AppData\Local\Programs\Python\Python38-32\lib\threading.p
y", line 890 in _bootstrap

Current thread 0x000013d8 (most recent call first):
<no Python frame>

为什么会这样?

编辑:这是我的代码和错误消息,尝试了 AST 的建议:

import tkinter as tk
import tkinter.ttk as ttk
import serial.tools.list_ports
from tkinter import scrolledtext 
import time
import serial
import threading
import continuous_threading


#to be used on our canvas
HEIGHT = 700
WIDTH = 800

#hardcoded baud rate
baudRate = 9600

# flag to be notified when application is terminated
stop=False


ser = serial.Serial('COM16', baudRate)
val1 = 0

def readSerial():
    global val1, stop
    if not stop:
       ser_bytes = ser.readline()
       ser_bytes = ser_bytes.decode("utf-8")
       val1 = ser_bytes
       scrollbar.insert("end", val1)
    else:
        return

t1 = continuous_threading.PeriodicThread(0.1, readSerial)
#----------------------------------------------------------------------

# --- functions ---

#the following two functions are for the seria port selection, on frame 1
def serial_ports():    
    return serial.tools.list_ports.comports()

def on_select(event=None):

    global COMPort
    COMPort = cb.get()
    print(COMPort)

def on_close():
    global stop
    stop=True
    root.destroy()



# --- functions ---


#--------------------------------------------------------------------------------
# --- main ---
root = tk.Tk() #here we create our tkinter window
root.title("Sensor Interface")
root.protocol('WM_DELETE_WINDOW', on_close)

#we use canvas as a placeholder, to get our initial screen size (we have defined HEIGHT and WIDTH)
canvas = tk.Canvas(root, height=HEIGHT, width=WIDTH)
canvas.pack()

#we use frames to organize all the widgets in the screen
# --- frame 1 ---
frame1 = tk.Frame(root)
frame1.place(relx=0, rely=0.05, relheight=0.03, relwidth=1, anchor='nw') #we use relheight and relwidth to fill whatever the parent is - in this case- root

label0 = tk.Label(frame1, text="Select the COM port that the device is plugged in: ")
label0.config(font=("TkDefaultFont", 8))
label0.place(relx = 0.1, rely=0.3, relwidth=0.3, relheight=0.5)


cb = ttk.Combobox(frame1, values=serial_ports())
cb.place(relx=0.5, rely=0.5, anchor='center')
# assign function to cmbobox
cb.bind('<<ComboboxSelected>>', on_select)
# --- frame 1 ---




# --- frame 2 ---
frame2 = tk.Frame(root, bg='#80c1ff') #remove color later
frame2.place(relx=0, rely=0.1, relheight=1, relwidth=1, anchor='nw')

# make a scrollbar
scrollbar = scrolledtext.ScrolledText(frame2)
scrollbar.place(relx=0, rely=0, relheight=1, relwidth=1, anchor='nw')
# --- frame 2 ---
#--------------------------------------------------------------------------------
t1.start() 
root.mainloop() #here we run our app

我得到的错误是我列为代码的第一个错误。

编辑 2: 当我执行 AST 的第二种方法时:

如果我关闭 GUI(按 X),它会在没有任何错误的情况下关闭,但提示卡在终端上 - 我无法输入任何内容,甚至键盘退出 (Control+C) 也不起作用。

如果程序运行并且我在终端 (Control+C) 中使用键盘退出退出,我会收到键盘中断错误(我列为代码的第二个错误)

根据我的说法,发生这种情况是因为可能有一个预定的线程在应用程序被销毁后执行,因此无法找到它必须更新的 GUI 元素。

我没有尝试过 运行 你的代码,但我认为这可能会有所帮助

将 window 删除与更新标志的函数相关联 (stop)

def on_close():
    global stop
    stop=True
    root.destroy()

root.protocol('WM_DELETE_WINDOW', on_close)
stop=False

并修改readSerial函数来检查是否相同

def readSerial():
    global val1,stop
    if not stop:
        ser_bytes = ser.readline()
        ser_bytes = ser_bytes.decode("utf-8")
        val1 = ser_bytes
        scrollbar.insert("end", val1)
    else:
        return

编辑

这是 threading.py 中引发异常的函数。

def _bootstrap(self):
    # Wrapper around the real bootstrap code that ignores
    # exceptions during interpreter cleanup.  Those typically
    # happen when a daemon thread wakes up at an unfortunate
    # moment, finds the world around it destroyed, and raises some
    # random exception *** while trying to report the exception in
    # _bootstrap_inner() below ***.  Those random exceptions
    # don't help anybody, and they confuse users, so we suppress
    # them.  We suppress them only when it appears that the world
    # indeed has already been destroyed, so that exceptions in
    # _bootstrap_inner() during normal business hours are properly
    # reported.  Also, we only suppress them for daemonic threads;
    # if a non-daemonic encounters this, something else is wrong.
    try:
        self._bootstrap_inner()
    except:
        if self._daemonic and _sys is None:
            return
        raise

因为在这种情况下,当所有non-daemonic线程都被杀死时,杀死线程是可以的,你可以尝试将线程设置为daemonic,以便自动处理异常。我认为这种方法不需要标志。

t1.daemon=True
t1.start()