Search code examples
pythonmultithreadingpython-2.7subprocessservo

Access function and stop threading media and servo subprocess in python


I have been doing some searching in this subject and there doesn't seem to be a very clear answer to my question. I currently am working a project where the user clicks on a button and once he does I call a python function, reply(), the function starts two different threads, a (stands for audio) and r (stands for routines) those two threads are supposed to work together and basically point to a direction in the map based as the narrator speaks:

def reply(index, path2, path3):
    a = threading.Thread(target=playaudio(path3))
    r = threading.Thread(target=routine(path2))
    r.start()
    a.start()

I am wondering if there is a way to access the function and stop those threads for both the playaudio and routine functions on user click of a stop button, so that if the user no longer wants to see it, all he needs to do is stop the presentation. The two functions are set up as follows:

# play audio function starts here:
def playaudio(path):
    try:
        subprocess.Popen(["mpg123", path])

    except Exception as ex:
        tkMessageBox.showinfo('Error', 'An error occurred.  ' + str(ex))

# routine function starts here
# routine controls the servo module
def routine(path):
    with open(path, 'rb') as f:
        reader = csv.reader(f)
        settings = list(reader)

    print(settings)
    i = 1
    while i < (len(settings) - 1):
        try:
            setall(int(settings[i][1]), int(settings[i][2]), int(settings[i][3]), int(settings[i][4]))
            delay = float(settings[i+1][0]) - float(settings[i][0]) - 0.015 #includes processing time
            time.sleep(delay)
            i += 1
        except Exception as ex:
            pass
    setall(int(settings[i][1]), int(settings[i][2]), int(settings[i][3]), int(settings[i][4]))

These will be initiated with a Tkinter.Button element on the main screen, and will play both the audio and control the servo module.

button = Tkinter.Button(window, text='Audio 4', command=lambda: reply(1, 'path/to/excel/file.csv', '/path/to/audio/file.mp3'))
button.config(width="30", height="5")
button.place(x=490, y=40)

For the stopping function I thought it would be a solution to add another Tkinter button element but with a different function to quit the subprocess whenever the user clicks on it.

stop_button = Tkinter.Button(window, text='Stop Audio', command=lambda: stop())
stop_button.config(width="30", height="5")
stop_button.place(x=490, y=360)

For the actual stop() function I attempted a few methods such as stop() and destroy() but the either the audio or the servo continue to run, or the actual program shuts off.

So my question is, what should I be doing differently? I would really appreciate any feedback in this problem.


Solution

  • Have you looked into the Queue module? You can send messages to threads with it.

    So, presuming you have q (an instance of Queue) which is accessible to your thread functions, your routine() thread function could look something like this:

    def routine(path):
    
        # skip some stuff...
    
        while i < (len(settings) - 1):
            if not q.empty():
                message = q.get()
                if message == 'stop':
                    break
    
            # rest of your function
    

    In your playaudio() function you'll want to hold onto the Popen object you created and use the `terminate() method to end the process. Take a look at the subprocess documentation for more information on how to do that.

    Then, when the user clicks the "stop" button, you can post a message to the queue:

    def stop():
        q.put('stop')
    

    As an aside, take look at the multiprocess module too. Python's GIL prevents threading from happening properly, and multiprocess is able to get around that and make use of multiple cores.