Search code examples
pythonpython-3.xmultithreadingpython-multithreading

How can I use a value returned by a function in a thread?


I'm working on a program that shows the user an image called test (with the method cal_picture(test, duration)) and tracks the position of their eyes simultaneously (with the method eyetracker(start, duration)). Both of these last for a certain amount of time determined by the variable duration. In order for both of these to run simultaneously, I put them in two threads using the _thread module:

    import _thread
    import time

    start = time.time()
    _thread.start_new_thread(cal_picture, (test, duration))
    _thread.start_new_thread(eyetracker, (start, duration))
    # Start of both threads
    while (time.time() - start) < (duration + 2):
        time.sleep(1)
    # waiting for the threads to run through

The method eyetracker(start, duration) returns a list of coordinates describing the detected positions of the user's pupils, however, I have been unable to call upon this list later on in the code.

I tried saving the list returned by the eyetracker function as a variable called positions, which was declared to be an empty list before the creation of the threads, given to the eyetracker function as a parameter and equated to the list it would return in the function itself. However, the positions variable given to the eyetracker function was only treated as a local variable within the eyetracker function and did not change the global variable of the same name (which makes sense in hindsight).

(In eyetracker method:)

    def eyetracker(positions, start, duration):
        ...
        positions = returned_pupil_positions

(In the part of the code that needs the pupil positions:)

    positions = []
    _thread.start_new_thread(cal_picture, (test, duration))
    _thread.start_new_thread(eyetracker, (positions, start, duration))
    # positions is still []

Next, I tried to equate positions to the line creating the thread for the eyetracker, hoping that it would return the eyetracker's return value. However, this didn't work either and positions was instead set to an integer value, the origins of which are unknown to me:

    _thread.start_new_thread(cal_picture, (test, duration))
    positions = _thread.start_new_thread(eyetracker, (start, duration))
    # positions is now an integer value for some reason, even though the eyetracker method returns a list  of values, which is exactly what I need positions to be

Long story short: How can I get my variable positions to be equal to the return value of the eyetracker method after I ran the latter using the _thread module?


Solution

  • Maybe the simplest way to get the positions ("simple" as in, fewest lines of code for you to write) would be to use a ThreadPoolExecutor to launch the threads. When you submit a task to the executor, it returns a Future object that you can use to fetch the value that was returned by the task:

    from concurrent.futures import ThreadPoolExecutor
    
    ...
    
    executor = ThreadPoolExecutor()
    cal_future = executor.submit(cal_picture, test, duration)
    eye_future = executor.submit(eyetracker, start, duration)
    cal_result = cal_future.result()  # waits for cal_picture task completion
    positions = eye_future.result()   # waits for eyetracker task completion
    

    Going deeper: The root of your problem is that the thread created by _thread.start_new_thread(eyetracker, (start, duration)) calls eyetracker(start,duration), and then it throws away the return value. So if you want to keep the return value, then somebody has to write a function that will call eyetracker, and then stash the return value in a place where the other thread can get it. If you do what I suggested above, and use ThreadPoolExecutor then "somebody" is whoever wrote the concurrent.futures module. But you can write it yourself if you want. Here's one way:

    from threading import Thread
    
    def eyetracker_main(result, start, duration):
        positions = eyetracker(start, duration)
        result.append(positions)
    
    result = []
    eyetracker_thread = Thread(eyetracker_main, (result, start, duration))
    ...maybe do other stuff here...
    eyetracker_thread.join()  # Wait for eyetracker to finish
    positions = result[0]
    

    Notice that I used the threading module instead of the _thread module. That's because I wanted to call join(), which as far as I know has no equivalent in the _thread module.


    But Wait!

    Why call eyetracker in a separate thread at all? Your main thread doesn't do anything while the eyetracker thread is running, and that's the entire point of using threads—to do more than one thing "at the same time." So if the main thread doesn't have anything to do while the eyetracker thread is running, then you might as well just call eyetracker from the main thread.

        import _thread
    
        _thread.start_new_thread(cal_picture, (test, duration))
        positions = eyetracker(start, duration)