间歇性Python线程错误,“主线程不在主循环中”

发布于 2021-01-29 16:35:02

中年父亲(电气工程师不是行业程序员)试图教我13岁的女儿电子学和编程。到目前为止,我喜欢Python。我正在构建一个程序,使用tkinter
GUI和DS18B20传感器显示整个房屋的温度。

我们从阅读书籍,在线研究以及使用Stack Overflow进行故障排除(此网站动摇了)中整理了以下程序。

现在我们很困惑,不断收到间歇性错误,当在Raspberry上加载空闲后第一次运行程序时,它可以正常工作。

第二次以及以后的所有时间,我们都会收到以下错误消息:

Traceback (most recent call last):
  File "/home/pi/Code-working-library/stackoverflow-paste.py", line 140, in <module>
    app.equipTemp.set(tempread)
  File "/usr/lib/python2.7/lib-tk/Tkinter.py", line 203, in set
    return self._tk.globalsetvar(self._name, value)
RuntimeError: main thread is not in main loop

注意,我们的理解是,为了拥有一个静态窗口并更新标签更新的温度,请从我们的传感器(DS18B20)上读取数据,我们需要使用一个线程。我们开始的示例代码的_init_语句前后只有一个下划线-
不知道为什么,如果再添​​加一个下划线,则会收到错误消息。我们用作基础的更新窗口代码来自Raspberry
Pi论坛

这是我们的代码:

from Tkinter import *
import tkFont
import os
import glob
import time
import subprocess
import re
import sys
import time
import threading
import Image 
import ImageTk

os.system('modprobe w1-gpio')
os.system('modprobe w1-therm')

#28-000005c6ba08

sensors = ['28-000005c6ba08'] 
sensors1 = ['28-000005c70f69']

def read_temp_raw():
    catdata = subprocess.Popen(['cat',device_file], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    out,err = catdata.communicate()
    out_decode = out.decode('utf-8')
    lines = out_decode.split('\n')
    return lines

def read_temp():
    lines = read_temp_raw()
    while lines[0].strip()[-3:] != 'YES':
        time.sleep(0.2)
        lines = read_temp_raw()
    equals_pos = lines[1].find('t=')
    if equals_pos != -1:
        temp_string = lines[1][equals_pos+2:]
        temp_c = float(temp_string) / 1000.0
        temp_f = temp_c * 9.0 / 5.0 + 32.0
        return temp_f

###########  build window  ###################

bground="grey"


class App(threading.Thread):

    def _init_(self):    
        threading.Thread._init_(self)
        self.start()

    def callback(self):
        self.root.quit()


    def run(self):

        #Make the window
        self.root = Tk() 
        self.root.wm_title("Home Management System")
        self.root.minsize(1440,1000)

        self.equipTemp = StringVar()   
        self.equipTemp1 = StringVar()
        self.equipTemp2 = StringVar()

        self.customFont = tkFont.Font(family="Helvetica", size=16)

        #   1st floor Image
        img = Image.open("HOUSE-PLANS-01.png") 
        photo = ImageTk.PhotoImage(img)

        Label1=Label(self.root, image=photo)
        Label1.place(x=100, y=100)

        #   2nd floor
        img2 = Image.open("HOUSE-PLANS-02.png")
        photo2 = ImageTk.PhotoImage(img2)

        Label1=Label(self.root, image=photo2)
        Label1.place(x=600, y=100)

        #   Basement image
        img3 = Image.open("HOUSE-PLANS-03.png")
        photo3 = ImageTk.PhotoImage(img3)

        Label1=Label(self.root, image=photo3)
        Label1.place(x=100, y=500)

        #   Attic Image
        img4 = Image.open("HOUSE-PLANS-04.png")
        photo4 = ImageTk.PhotoImage(img4)

        Label1=Label(self.root, image=photo4)
        Label1.place(x=600, y=500)

        #   House Isometric Image
        img5 = Image.open("house-iso.png")
        photo5 = ImageTk.PhotoImage(img5)

        Label1=Label(self.root, image=photo5)
        Label1.place(x=1080, y=130)

        #Garage Temp Label
        Label2=Label(self.root, textvariable=self.equipTemp, width=6, justify=RIGHT, font=self.customFont)
        Label2.place(x=315, y=265)



        print "start monitoring and updating the GUI"

        self.root.mainloop() #start monitoring and updating the GUI



###########  Start Loop    ###################

print "starting app"

app = App()
app.start()

print "app started"


###################  Begin ds18b20 function  ##############

while True:

    #   28-000005c6ba08
    i = "28-000005c6ba08"
    base_dir = '/sys/bus/w1/devices/'
    device_folder = glob.glob(base_dir + i)[0]
    device_file = device_folder + '/w1_slave'

    tempread=round(read_temp(),1)


    app.equipTemp.set(tempread)
    time.sleep(5)

    ##################### END ds18b20 Function  ######
关注者
0
被浏览
152
1 个回答
  • 面试哥
    面试哥 2021-01-29
    为面试而生,有面试问题,就找面试哥。

    您需要在主线程中运行GUI代码,而温度读取代码需要在后台线程中。仅在主线程中更新GUI是安全的,因此您可以通过将从后台线程读取的温度数据通过传递回主线程Queue,并使主线程使用以下命令定期检查队列中的数据self.root.after()

    from Tkinter import *
    import tkFont
    import os
    import glob
    import time
    import threading
    import Image 
    import Queue
    
    
    def update_temp(queue):
        """ Read the temp data. This runs in a background thread. """
        while True:
            #   28-000005c6ba08
            i = "28-000005c6ba08"
            base_dir = '/sys/bus/w1/devices/'
            device_folder = glob.glob(base_dir + i)[0]
            device_file = device_folder + '/w1_slave'
    
            tempread=round(read_temp(),1)
    
            # Pass the temp back to the main thread.
            queue.put(tempread)
            time.sleep(5)
    
    class Gui(object):
        def __init__(self, queue):
            self.queue = queue
    
            #Make the window
            self.root = Tk() 
            self.root.wm_title("Home Management System")
            self.root.minsize(1440,1000)
    
            self.equipTemp = StringVar()   
            self.equipTemp1 = StringVar()
            self.equipTemp2 = StringVar()
    
            self.customFont = tkFont.Font(family="Helvetica", size=16)
    
            #   1st floor Image
            img = Image.open("HOUSE-PLANS-01.png") 
            photo = ImageTk.PhotoImage(img)
    
            Label1=Label(self.root, image=photo)
            Label1.place(x=100, y=100)
    
            #   2nd floor
            img2 = Image.open("HOUSE-PLANS-02.png")
            photo2 = ImageTk.PhotoImage(img2)
    
            Label1=Label(self.root, image=photo2)
            Label1.place(x=600, y=100)
    
            #   Basement image
            img3 = Image.open("HOUSE-PLANS-03.png")
            photo3 = ImageTk.PhotoImage(img3)
    
            Label1=Label(self.root, image=photo3)
            Label1.place(x=100, y=500)
    
            #   Attic Image
            img4 = Image.open("HOUSE-PLANS-04.png")
            photo4 = ImageTk.PhotoImage(img4)
    
            Label1=Label(self.root, image=photo4)
            Label1.place(x=600, y=500)
    
            #   House Isometric Image
            img5 = Image.open("house-iso.png")
            photo5 = ImageTk.PhotoImage(img5)
    
            Label1=Label(self.root, image=photo5)
            Label1.place(x=1080, y=130)
    
            #Garage Temp Label
            Label2=Label(self.root, textvariable=self.equipTemp, width=6, justify=RIGHT, font=self.customFont)
            Label2.place(x=315, y=265)
    
            print "start monitoring and updating the GUI"
    
            # Schedule read_queue to run in the main thread in one second.
            self.root.after(1000, self.read_queue)
    
        def read_queue(self):
            """ Check for updated temp data"""
            try:
                temp = self.queue.get_nowait()
                self.equipTemp.set(temp)
            except Queue.Empty:
                # It's ok if there's no data to read.
                # We'll just check again later.
                pass
            # Schedule read_queue again in one second.
            self.root.after(1000, self.read_queue)
    
    if __name__ == "__main__":
        queue = Queue.Queue()
        # Start background thread to get temp data
        t = threading.Thread(target=update_temp, args=(queue,))
        t.start()
        print "starting app"
        # Build GUI object
        gui = Gui(queue)
        # Start mainloop
        gui.root.mainloop()
    

    编辑:

    之后实际上正在看看Tkinter的源代码,以及Python的bug跟踪系统,似乎与几乎所有其它的GUI库那里,Tkinter的
    意是线程安全的,只要你在主线程中运行的主循环的应用程序。见我添加的答案在这里获得更多信息,或者直接去解决问题,关于Python的bug跟踪系统的Tkinter的线程安全性在这里。如果tkinter源代码和Python的错误跟踪器是正确的,那意味着只要您在主线程中运行mainloop,就可以gui.equipTemp.set()直接从温度读取线程中愉快地调用-Queue不需要。在我的测试中,确实确实很好。



知识点
面圈网VIP题库

面圈网VIP题库全新上线,海量真题题库资源。 90大类考试,超10万份考试真题开放下载啦

去下载看看