我正在尝试编写一个从串口连接获取数据的程序,并根据该数据实时自动更新Tkinter窗口。
我尝试为窗口创建一个单独的线程,定期从主线程获取当前数据并更新窗口,如下所示:
serialdata = []
data = True
class SensorThread(threading.Thread):
def run(self):
serial = serial.Serial('dev/tty.usbmodem1d11', 9600)
try:
while True:
serialdata.append(serial.readline())
except KeyboardInterrupt:
serial.close()
exit()
class GuiThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.root = Tk()
self.lbl = Label(self.root, text="")
def run(self):
self.lbl(pack)
self.lbl.after(1000, self.updateGUI)
self.root.mainloop()
def updateGUI(self):
msg = "Data is True" if data else "Data is False"
self.lbl["text"] = msg
self.root.update()
self.lbl.after(1000, self.updateGUI)
if __name == "__main__":
SensorThread().start()
GuiThread().start()
try:
while True:
# A bunch of analysis that sets either data = True or data = False based on serialdata
except KeyboardInterrupt:
exit()
运行它会给我这个错误:
线程Thread-2中的异常: Traceback(最近一次调用最后一次): 在__bootstrap_inner中输入文件“/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/threading.py”,第522行 self.run() 文件“analysis.py”,第52行,在运行中 self.lbl1.pack() 在pack_configure中输入文件“/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/lib-tk/Tkinter.py”,第1764行 + self._options(cnf,kw)) RuntimeError:主线程不在主循环中
当我谷歌这个错误时,我主要得到的帖子是人们试图从两个不同的线程与窗口交互,但我不认为我这样做。有任何想法吗?非常感谢!
答案 0 :(得分:7)
不要从线程运行TK gui - 从主进程运行它。我把你的例子捣碎成了一个展示原则的东西
from time import sleep
import threading
from Tkinter import *
serialdata = []
data = True
class SensorThread(threading.Thread):
def run(self):
try:
i = 0
while True:
serialdata.append("Hello %d" % i)
i += 1
sleep(1)
except KeyboardInterrupt:
exit()
class Gui(object):
def __init__(self):
self.root = Tk()
self.lbl = Label(self.root, text="")
self.updateGUI()
self.readSensor()
def run(self):
self.lbl.pack()
self.lbl.after(1000, self.updateGUI)
self.root.mainloop()
def updateGUI(self):
msg = "Data is True" if data else "Data is False"
self.lbl["text"] = msg
self.root.update()
self.lbl.after(1000, self.updateGUI)
def readSensor(self):
self.lbl["text"] = serialdata[-1]
self.root.update()
self.root.after(527, self.readSensor)
if __name__ == "__main__":
SensorThread().start()
Gui().run()
答案 1 :(得分:2)
您需要将GUI放在主线程中,并使用单独的线程轮询串行端口。从串行端口读取数据时,可以将其推送到Queue对象。
在主GUI线程中,您可以设置轮询以定期检查队列,方法是使用after
来安排轮询。调用一个排空队列的函数,然后用after
调用自己来有效地模拟一个无限循环。
如果来自传感器的数据速度相当慢,并且您可以在不阻塞的情况下轮询串行端口,则可以在主线程中完成所有操作 - 而不是从队列推送和拉出,线程可以查看是否有可用的数据,如果有,则读取它。只有在没有阻塞的情况下才能读取,否则只能在等待数据时冻结GUI。
例如,你可以让它像这样工作:
def poll_serial_port(self):
if serial.has_data():
data = serial.readline()
self.lbl.configure(text=data)
self.after(100, self.poll_serial_port)
以上将每秒检查串口10次,一次关闭一个项目。当然,您必须根据实际数据条件进行调整。这假设您有一些像has_data
这样的方法,当且仅当读取不会阻塞时才能返回True。