5

我正在尝试编写一个从串行端口连接获取数据并根据该数据实时自动更新 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 中的异常:回溯(最后一次调用):文件“/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/threading.py”,第 522 行,在 __bootstrap_inner self. run() 文件“analysis.py”,第 52 行,在运行 self.lbl1.pack() 文件“/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/lib-tk/Tkinter .py",第 1764 行,在 pack_configure + self._options(cnf, kw)) RuntimeError:主线程不在主循环中

当我在谷歌上搜索这个错误时,我大多会收到人们试图从两个不同的线程与窗口交互的帖子,但我认为我没有这样做。有任何想法吗?非常感谢!

4

2 回答 2

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()
于 2012-05-13T20:52:02.333 回答
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 。

于 2012-05-13T21:33:40.440 回答