0

I've made a Python program which reads the file and then sends/receives the data towards/from the microcontroller and everything worked well until I added a menu to display short instructions.

Since the UART communication has to run in a separate thread I used threading and StringVar() to access the data from the main thread.

To demonstrate the problem I've made a short example which has nothing to do with microcontrollers.

The steps to reproduce the problem:

  1. Click the Next screen radio button to open the second screen (at the initial screen the menu works well)
  2. Click Help > Instructions

After (or sometimes even before) closing the message box the program will crash with:

TclStackFree: incorrect freePtr. Call out of sequence?

This application has requested the Runtime to terminate it in an unusual way.
Please contact the application's support team for more information.

Note:
In the original program where there are more UI elements the program always crashes before showing the message box and after removing even more UI elements the program doesn't crash every time - that's why I've left some "redundant" labels. When some more labels are added the program will crash every time.

I have narrowed the cause of the crash to:

displayVal.set()

in checkForData() function. By removing that instruction everything works well.

Moreover, after removing displayVal.trace("w", displayVal_trace) the program will not crash anymore but opening the menu will still temporarily freeze the working thread.

I thought after displayVal.set() Tkinter is trying to update the label but can't because of showing the menu - however, the problem remained even after I removed label_data = Label(up2, textvariable = displayVal).

Here is the stripped down code tested with Python 2.7:

from Tkinter import *
import tkMessageBox
import threading
import time

threadRun = True
checkDelay = 0.5

def checkForData():
    global threadRun, checkDelay
    print "Simulating thread for receiving messages from UART"
    while threadRun == True:
        print time.time()
        displayVal.set(time.time())     # <-- if removed the menu works OK (no crash)
        time.sleep(checkDelay)
    print "No more receiving of messages"

def listenForData():
    t = threading.Thread(target=checkForData)
    t.daemon = False
    t.start()

def stopListening():
    global threadRun, checkDelay
    threadRun = False
    time.sleep(checkDelay + 0.1)

def exit_handler():
    print "Exiting..."
    stopListening()
    root.destroy()

root = Tk()
right = int((root.winfo_screenwidth() - 600) / 2)
down = int(root.winfo_screenheight() / 3 - 400 / 2)
root.geometry("600x400+%d+%d" % (right, down))
root.resizable(width = False, height = False)
root.protocol("WM_DELETE_WINDOW", exit_handler)

displayVal = StringVar()
displayVal.set("nothing")

def setupView():
    global masterframe
    masterframe = Frame()
    masterframe.pack(fill=BOTH, expand=True)
    selectPort()    # the 1st screen for selecting COM ports

def selectPort():
    global masterframe, radioVar
    # remove everything from the frame
    for child in masterframe.winfo_children():
        child.destroy()
    radioVar = StringVar()

    l1 = Label(masterframe, text = "Select...")
    l1.pack(pady=(50, 20))

    # this would be a list of detected COM ports
    lst = ["Next screen"]

    if len(lst) > 0:
        for n in lst:
            r1 = Radiobutton(masterframe, text=n, variable=radioVar, value=n)
            r1.config(command = next_screen)
            r1.pack()

def mainScreen():
    global masterframe, term, status

    # remove previous screen from the frame
    for child in masterframe.winfo_children():
        child.destroy()
    
    up1 = Frame(masterframe)
    up1.pack(side=TOP)

    up2 = Frame(masterframe)
    up2.pack()

    terminal = Frame(masterframe)
    terminal.pack()

    down = Frame(masterframe)
    down.pack(side=BOTTOM, fill=BOTH)

    label_top = Label(up1, text="Something")
    label_top.pack(pady=5)

    label_data = Label(up2, textvariable = displayVal)
    label_data.pack(pady=(10, 0))

    term = Text(terminal, height=10, width=35, bg="white")
    term.pack()
    term.tag_config("red", foreground="red")
    term.tag_config("blue", foreground="blue")
    term.insert(END, "The file has been read\n", "red")
    term.insert(END, "File contents:\n")
    term.insert(END, data)

    status = Label(down, text="Status...", bd=1, relief=SUNKEN, anchor=W, bg="green")
    status.pack(fill=X)

    displayVal.trace("w", displayVal_trace)     # <-- if removed only temporary freeze but no crash

def displayVal_trace(name, index, mode):
    global term

    if(displayVal.get() != "NOTHING"):
        term.insert(END, "\nReceived: ", "blue")
        term.insert(END, displayVal.get())
        term.see(END)

def next_screen():
    listenForData()
    mainScreen()

def stop():
    stopListening()

def instructions():
    tkMessageBox.showinfo("Help", "This is help")

main_menu = Menu(root)
root.config(menu = main_menu)
help_menu = Menu(main_menu)
main_menu.add_cascade(label="Help", menu=help_menu)
help_menu.add_command(label="Instructions", command=instructions)

data = [[1], [2], [3]]      # this would be the data read from the file
b1 = data[0][0]
b2 = data[1][0]
b3 = data[2][0]
print "Read from file:", b1, b2, b3

setupView()
root.mainloop()
Chupo_cro
  • 698
  • 1
  • 7
  • 21
  • 2
    Have you read this? https://stackoverflow.com/questions/38767355/callback-to-python-function-from-tkinter-tcl-crashes-in-windows/38767665#38767665 – Bryan Oakley Aug 06 '20 at 03:44
  • @BryanOakley: Does that mean `StringVar` variables are not allowed to use in separate threads? I thought `stringvariable.set()` is a built in way to communicate between threads avoiding queues, signals and similar. I saw quite a few examples of communication between threads using `StringVar` when receiving/sending messages from/to a microcontroller, that's why I thought `StringVar` could be used instead of a queue. If that can't work then I might just remove the help menu from the second screen. – Chupo_cro Aug 06 '20 at 04:02

0 Answers0