I am programming a GUI with tkinter that controls the communication to multiple motor controllers that are connected serially via RS485. I have a function (get_status()) that requests the status (motor running, motor position, position reached, …) of the controllers via pySerial and displays the uptated status on the UI. I want to run this funktion every second.
The problem is, while the serial communication is running, the UI freezes, and that happening every second makes the program very laggy to use.
I already tried to run the get_status funktion in its own thread but it didn’t make any difference.
I am relatively new to python and not sure if I really understood threading in the first place.
What I tried was this:
import serial, threading, tkinter as tk, serial.tools.list_ports, time, os, tkinter.filedialog from tkinter import ttk class myThread (threading.Thread): def __init__(self): threading.Thread.__init__(self) def run(self): print("Starting " + self.name) get_status() print("Exiting " + self.name) ser = serial.Serial() thread = myThread() def get_status(): status = ask_status() update_status_leds(status) window.after(1000, thread.run) com_port = chs_com_port.get().split(" ") ser.port = com_port[0] ser.baudrate = 19200 ser.bytesize = serial.SEVENBITS ser.parity = serial.PARITY_EVEN ser.timeout = 0.2 ser.open() thread.run()
For this question I heaviliy simplified the get_status funktion but this is basically how it works.
As I said, even now that get_status() supposedly runs in a new thread, it is still blocking the UI mainloop from updating resulting in a freeze while the serial communication is running.
Any help is greatly appreciated and sorry to any veteran who has to scratch his head over my shitty code.
EDIT: I found a semi good way that kind of works. I made another function status_loop()
that creates a new thread object and starts the thread. This funktion gets called by window.after(1000,status_loop)
in the get_status()
funktion
The new code looks like this:
import serial, threading, tkinter as tk, serial.tools.list_ports, time, os, tkinter.filedialog from tkinter import ttk class myThread (threading.Thread): def __init__(self): threading.Thread.__init__(self) def run(self): print("Starting " + self.name) get_status() print("Exiting " + self.name) ser = serial.Serial() def status_loop(): thread = myThread() thread.start() def get_status(): status = ask_status() update_status_leds(status) window.after(1000, status_loop) com_port = chs_com_port.get().split(" ") ser.port = com_port[0] ser.baudrate = 19200 ser.bytesize = serial.SEVENBITS ser.parity = serial.PARITY_EVEN ser.timeout = 0.2 ser.open() status_loop()
Advertisement
Answer
You use thread in a wrong way:
start()
should be used instead ofrun()
to start the thread- should not call
thread.run()
insideget_status()
. Use while loop instead
import time ... class myThread (threading.Thread): def __init__(self): threading.Thread.__init__(self) def run(self): print("Starting " + self.name) get_status() print("Exiting " + self.name) ... def get_status(): while True: status = ask_status() update_status_leds(status) time.sleep(1) ... thread = myThread() thread.daemon = True thread.start() # start the thread
If the tasks inside get_status()
are not time-consuming tasks, then simply use after()
instead of thread:
def get_status(): status = ask_status() update_status_leds(status) window.after(1000, get_status) ... get_status() # start the after loop