简体   繁体   English

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

[英]Intermittent Python thread error, “main thread is not in main loop”

Middle aged dad (electrical engineer not programmer by trade) trying to teach my 13 year old daughter electronics and programming. 中年爸爸(电气工程师不是贸易程序员)试图教我13岁的女儿电子和编程。 So far, I love Python. 到目前为止,我喜欢Python。 I am building a program to display temperatures throughout our house with tkinter GUI and DS18B20 sensors. 我正在构建一个程序,通过tkinter GUI和DS18B20传感器显示整个房屋的温度。

We've pieced together the program below from reading books, online research and using Stack Overflow for trouble-shooting bugs (this site rocks!). 我们将下面的程序拼凑起来,从阅读书籍,在线研究和使用Stack Overflow来解决问题(这个网站摇滚!)。

Now we're stumped, we keep getting an intermittent error, when we run the program the first time after loading idle on our Raspberry, it works fine. 现在我们感到难过,我们不断出现间歇性错误,当我们在Raspberry上加载空闲后第一次运行程序时,它运行正常。

The second time, and all subsequent times, we get this error message: 第二次,以及随后的所有时间,我们收到此错误消息:

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

Note, our understanding is that in order to have a static window and update labels updated temps read off our sensor (DS18B20) we needed to use a thread. 注意,我们的理解是,为了获得一个静态窗口并更新标签更新的温度读取我们的传感器(DS18B20),我们需要使用一个线程。 The sample code we started with has the _init_ statements with only one underscore preceding and trailing - not sure why, if I add a second underscore, I get error messages. 我们开始的示例代码具有_init_语句,前面和后面只有一个下划线 - 不知道为什么,如果我添加第二个下划线,我会收到错误消息。 The updating window code we used as our basis came from Raspberry Pi forum 我们用作基础的更新窗口代码来自Raspberry Pi论坛

Here is our code: 这是我们的代码:

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  ######

You need to run the GUI code in the main thread, and your temperature reading code needs to be in the background thread. 您需要在主线程中运行GUI代码,并且您的温度读取代码需要位于后台线程中。 It's only safe to update the GUI in the main thread, so you can pass the temperature data you're reading from the background thread back to the main thread via a Queue , and have the main thread periodically check for data in the queue using self.root.after() : 在主线程中更新GUI是唯一安全的,因此您可以通过Queue将您正在从后台线程读取的温度数据传递回主线程,并让主线程使用self.root.after()定期检查队列中的数据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()

Edit: 编辑:

After actually taking a look at the tkinter source code, as well as the Python bug tracker, it appears that unlike almost every other GUI library out there, tkinter is intended to be thread-safe, as long you run the mainloop in the main thread of the application. 实际上正在看看Tkinter的源代码,以及Python的bug跟踪系统后,似乎与几乎所有其它的GUI库那里,Tkinter的意图是线程安全的,只要你运行在主线程的主循环申请。 See the answer I added here for more info, or go straight to the resolved issue about tkinter's thread safety on the Python bug tracker here . 见我添加的答案在这里获得更多信息,或者直接去解决问题,关于Python的bug跟踪系统的Tkinter的线程安全性在这里 If the tkinter source and Python's bug tracker are correct, that would mean that as long as you run the mainloop in the main thread, you can happily call gui.equipTemp.set() directly from your temperature reading thread - no Queue required. 如果tkinter源和Python的bug跟踪器是正确的,那就意味着只要你在主线程中运行mainloop,你就可以直接从你的温度读取线程调用gui.equipTemp.set() - 不需要Queue And in my testing, that did indeed work just fine. 在我的测试中,确实可以正常工作。

GUI toolkits are not threadsafe. GUI工具包不是线程安全的。 You can only built and change your GUI from the main thread. 您只能从主线程构建和更改GUI。 Since reading the temperature does not take that long, you can remove all the threading code and use the after -method from Tk. 由于读取温度的时间不长,您可以删除所有线程代码并使用Tk的after -method。

Your read_temp_raw function is very complicated: 你的read_temp_raw函数非常复杂:

def read_temp_raw():
    with open(device_file) as temp:
        return temp.read().split('\n')

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM