Skip to content
Advertisement

Can’t stop thread at all (unless I cause an exception)

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). Defining global 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 defining serial_flag = Value('i', 0) then in the loop checking while 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().

User contributions licensed under: CC BY-SA
4 People found this is helpful
Advertisement