So I am working on a GUI (PyQt5) and I am multi-threading to be able to do data acquisition and real-time plotting concurrently.
Long story short, all works fine apart from stopping the thread that handles the data acquisition, which loops continuously and calls PySerial to read the com port. When a button on the GUI is pressed, I want to break the while loop within the thread so as to stop reading the com port and allow the com port to be closed safely.
Currently, none of the methods I have tried manage to gracefully exit the while loop inside the thread, which causes all kinds of errors with the PySerial library and the closed com port/ attempted reading in the thread. Here is what I have tried:
- Using a class variable (
self.serial_flag
) and changing its state when the button is pressed. The thread loop then looks like this:while self.serial_flag:
- Using a global variable (
serial_flag = False
at top of the script). Definingglobal serial_flag
at the top of the threaded function and same condition:while serial_flag:
- Using a shared memory variable:
from multiprocessing import Value
, then definingserial_flag = Value('i', 0)
then in the loop checkingwhile serial_flag.value == 0:
- Using
threading.Event
to set an event and use that as a break condition. Defining:serial_flag = threading.Event()
and inside the thread while loop:if serial_flag.is_set(): break
None of these seem to work in breaking the while loop and I promise I have done my homework in researching solutions for this type of thing – I feel like there is something basic that I am doing wrong with my multithreading application. Here are the parts of the GUI that call/ deal with the thread (with my latest attempt using threading.Event):
import threading, queue serial_flag = threading.Event() def serial_run_data(self): cntr = 0 while True: if serial_flag.is_set(): print("flag was set") break value = self.ser.read_until(self.EOL) # old EOL: rn, new: oi if len(value) == 21 and self.EOL in value: self.queueRaw.put_nowait(value) else: print(value) cntr = cntr + 1 if cntr == 50: self.data_ready = True cntr = 0 def start_plot(self): # Start/ Stop Button # Start Button if self.start_button.text() == 'START': self.serial_setup() if not self.error: self.serial_flag = True self.t = threading.Thread(target=self.serial_run_data) self.t.start() self.timer.start() self.start_button.setText('STOP') # Stop Button elif self.start_button.text() == 'STOP': if not self.error: self.serial_flag = False serial_flag.set() self.timer.stop() print(self.t.is_alive()) self.ser.close() self.start_button.setText('START')
Any feedback is appreciated (I will make a minimally-reproducible example if what I have provided isn’t sufficient). Cheers!
Advertisement
Answer
The problem occurs because you are closing the serial port without waiting for the thread to terminate. Although you set the event, that part of the code might not be reached, since reading from the serial port is still happening. When you close the serial port, an error in the serial library occurs.
You should wait for the thread to terminate, then close the port, which you can do by adding
self.t.join()
after serial_flag.set()
.