1

I have a Python program that does the following: 1) endlessly wait on com port a command character 2) on character reception, launch a new thread to execute a particular piece of code

What I would need to do if a new command is received is: 1) kill the previous thread 2) launch a new one

I read here and there that doing so is not the right way to proceed. What would be the best way to do this knowing that I need to do this in the same process so I guess I need to use threads ...

DKlex
  • 21
  • 2

4 Answers4

1

I would suggest you two differente approaches:

  • if your processes are both called internally from a function, you could set a timeout on the first function.
  • if you are running external script, you might want to kill the process.
Community
  • 1
  • 1
mabe02
  • 2,676
  • 2
  • 20
  • 35
  • I don't see the point with the timeout solution since I don't want the function to exit after a while but I want it to exit once another one is called. – DKlex Jan 08 '16 at 09:02
  • Did you try multiprocessing then? Otherwise you can make the call to the second function as a last statement in the first one. If I don't understand, could you please post an extract of your code? – mabe02 Jan 08 '16 at 09:22
  • I just posted an example below. Hope it's clearer now. – DKlex Jan 08 '16 at 09:30
0

May want to run the serial port in a separate thread. When it receives a byte put that byte in a queue. Have the main program loop and check the queue to decide what to do with it. From the main program you can kill the thread with join and start a new thread. You may also want to look into a thread pool to see if it is what you want.

ser = serial.Serial("COM1", 9600)
que = queue.Queue()

def read_serial(com, q):
    val = com.read(1)
    q.put(val)

ser_th = threading.Thread(target=read_serial, args=(ser, que))
ser_th.start()

th = None
while True:
    if not que.empty():
        val = que.get()
        if val == b"e":
            break # quit
        elif val == b"a":
            if th is not None:
                th.join(0) # Kill the previous function
            th = threading.Thread(target=functionA)
            th.start()
        elif val == b"b":
            if th is not None:
                th.join(0) # Kill the previous function
            th = threading.Thread(target=functionB)
            th.start()
        elif val == b"c":
            if th is not None:
                th.join(0) # Kill the previous thread (functionA)
            th = threading.Thread(target=functionC)
            th.start()

try:
    ser.close()
    th.join(0)
except: 
    pass

If you are creating and joining a lot of threads you may want to just have a function that checks what command to run.

running = True
def run_options(option):
    if option == 0:
        print("Running Option 0")
    elif option == 1:
        print("Running Option 1")
    else:
        running = False

while running:
    if not que.empty():
        val = que.get()
        run_options(val)
justengel
  • 6,132
  • 4
  • 26
  • 42
  • I feel your answer can help me but I don't know how to adapt it to the particular case of my code I posted afterwards ... an idea? – DKlex Jan 11 '16 at 09:21
  • You would have to have a way to kill the function that was running, so then you can end the thread. I changed the first option I posted to show you how it would work. – justengel Jan 11 '16 at 12:55
  • I just tried your new proposal but I can't get the previous thread to be killed. It still continues to run once the new command is received. I don't understand how the th.join(0) could kill any function ... – DKlex Jan 11 '16 at 14:24
  • you have to devise a way to kill your function. The thread join will end a thread when the thread exits it's run method (functionA). There are some ways to terminate threads, but they are not recommended. – justengel Jan 11 '16 at 14:28
  • Ok that's why the join(0) did not kill anything. What I want to be able to do is to exit from my function as soon as a command is received! I'm trying to find a way to insert some common code in every killable functions such as a while loop which could exit on certain condition. – DKlex Jan 11 '16 at 14:32
  • I think even a conditional while loop would not work since in the loop itself I could call a synchronous service! The only solution is really to be able to kill the thread! – DKlex Jan 11 '16 at 14:45
0

Let me try to be more precise in my question by adding an example of my code structure.

Suppose synchronous functionA is still running because waiting internally for a particular event, if command "c" is received, I need to stop functionA and launch functionC.

 def functionA():
    ....
    ....
    call a synchronous serviceA that can take several seconds even more to execute
    ....
    ....


def functionB():
    ....
    ....
    call a synchronous serviceB that nearly returns immediately
    ....
    ....


def functionC():
    ....
    ....
    call a synchronous serviceC
    ....
    ....    


#-------------------

def launch_async_task(function):

    t = threading.Thread(target=function, name="async")
    t.setDaemon(True)
    t.start()


#------main----------

while True:
    try:
        car = COM_port.read(1)

        if      car == "a":
                    launch_async_task(functionA)

        elif    car == "b":
                    launch_async_task(functionB)

        elif    car == "c":
                    launch_async_task(functionC)
DKlex
  • 21
  • 2
0

Ok, I finally used a piece of code that uses ctypes lib to provide some kind of killing thread function. I know this is not a clean way to proceed but in my case, there are no resources shared by the threads so it shouldn't have any impact ...

If it can help, here is the piece of code that can easily be found on the net:

def terminate_thread(thread):
    """Terminates a python thread from another thread.

    :param thread: a threading.Thread instance
    """
    if not thread.isAlive():
        return

    exc = ctypes.py_object(SystemExit)
    res = ctypes.pythonapi.PyThreadState_SetAsyncExc(
        ctypes.c_long(thread.ident), exc)
    if res == 0:
        raise ValueError("nonexistent thread id")
    elif res > 1:
        # """if it returns a number greater than one, you're in trouble,
        # and you should call it again with exc=NULL to revert the effect"""
        ctypes.pythonapi.PyThreadState_SetAsyncExc(thread.ident, None)
        raise SystemError("PyThreadState_SetAsyncExc failed")
DKlex
  • 21
  • 2