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: \r\n, new: o\i
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!
CodePudding user response:
Try passing the threading.Event
object as an argument to the serial_run_data
by starting the thread the following way:
self.t = threading.Thread(target=self.serial_run_data, args=(serial_flag,))
and adding the event to the arguments of serial_run_data
method. Also defining the threading.Event
on the class level is probably better, since it puts it in a narrower scope (I assume you deleted the rest of the class code, since you left self
as an argument to the methods).
Setting the event should now break the loop.
EDIT (Additional information provided)
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()
.