关闭我的 tkinter 串行应用程序,给了我一个例外
Posted
技术标签:
【中文标题】关闭我的 tkinter 串行应用程序,给了我一个例外【英文标题】:Closing my tkinter serial app, gives me an exception 【发布时间】:2021-02-26 23:15:42 【问题描述】:我有这个程序可以从串行中获取数据并将它们显示在 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 被破坏并产生错误后,周期性线程仍会继续运行,因为它不再存在。您可以尝试使用标志来避免这种情况。 嗯,谢谢,但我不确定我是否理解如何在这里使用标志。 【参考方案1】:据我说,这是因为可能有一个计划线程在应用程序被销毁后执行,因此无法找到它必须更新的 GUI 元素。
我没有尝试运行您的代码,但我认为这可能会有所帮助
将窗口删除与更新标志的函数相关联 (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()
【讨论】:
谢谢。尝试了相信它会工作的代码,但问题仍然存在。使用完整代码(以及您的建议)和错误消息编辑了问题。 @user1584421 我目前无法完全重现该错误,但我已经用另一种可能的方法更新了答案,如果可行,请告诉我。另外,您能否尝试将您的代码减少到 minimal, reproducible example 以便更容易使用。 对不起,escessive代码。它仍然不起作用。当我关闭 GUI(按 X)时,它会在没有任何错误的情况下关闭,但提示卡在终端上 - 我无法输入任何内容,甚至键盘退出 (Control+C) 也不起作用。当程序运行并且我使用终端中的键盘退出 (Control+C) 退出时,我得到 Doesnt fit in a comment 所以请参阅问题中的 EDIT2 @user1584421 到目前为止所有的错误都是一模一样的,请不要一遍又一遍地填写问题,将其剥离以包含一次,这样其他人会更容易阅读。我现在确实注意到了这个问题,但我无法得到继续发生的错误。我只是好奇是否有使用线程的特定需要?你看过after
方法吗?
我很抱歉...我没有注意到它完全一样...我会清理我的问题。我的应用程序所做的只是从串行(从 arduino 传入)中获取数据并将它们显示到 tkinter scrollText 框架。本质上,它取代了 arduino IDE,因为我只想接收串行。由于我是 Tkinter 的新手(几乎是 python 的新手),我看到了一些有关如何完成此操作的 youtube 教程。我做到了,但它没有刷新,教程提到我必须创建线程,因为串行阻塞。这就是我这样做的原因。你知道其他方法吗?以上是关于关闭我的 tkinter 串行应用程序,给了我一个例外的主要内容,如果未能解决你的问题,请参考以下文章