3
votes

Could someone explain the concept behind how to pass data between asynchronous calls in python?

I have this scenario:
I have a main procedure (mainthread) and then I run another async call that adds two numbers() and sleeps for some time. The expectation is to have main thread wait until calc and sleeping are done. In terms of pseudo code this might look like this:

def example():
    def my_calc(x,y):
        z = x+ y
        time.sleep(2)
        return z  #this should get pushed to queue, but for simplicity we use 'return z'

    z = asyncSomething.callInThread(my_calc, 2, 20)  #assume we get z from queue
    return z+10

def runner():  #our main function
    print 'start'
    z = example()
    print 'result is {0}'.format(z)

How do I made the last print wait for z? I tried to use threading.Event() and played with set, wait, and clear, but they block everything.

I do not think my case is unique and there has to be a neat systematic way of doing it. I looked into twisted reactor but it was partially successful. My actual code involved GUI that has to wait for results from async processes and then self- and auto- update...but I think the example depicts the bulk of the problem

I think I am missing the actual concept of how to work asynchronously in python.

2
Are you using Python 2? Looks like Python 3's async and await keywords can't help here ._. - GeeTransit
@Geetransit, it is 2.7. I used 'async' figuratively not literally - JavaFan
is there a reason you were even using python 2.7 in 2019? - Krupip
@opa, ecosystem I work with is still using 2.7 in 2019; hence my question - JavaFan

2 Answers

1
votes

If your main thread has progressed as far as it meaningfully can without the result from the worker thread and you want to block, you can either use a threading.Event to wait for a certain point in the worker thread's execution or thread.join (where thread is a threading.Thread instance) to wait for the worker thread to complete:

class Example(object):
    def my_calc(self, x, y):
        time.sleep(2)
        self._z = x + y # or push the result to a queue

    def example(self):
        thread = threading.Thread(target=self.my_calc, args=(2, 20))
        thread.start()
        # any other useful work could go here while the worker runs
        thread.join()
        return self._z + 10 # or grab the result from a queue

def runner():
    print "start"
    z = Example().example()
    print "result is {0}".format(z)

However, in the context of a GUI, it is unlikely that your main thread has progressed as far as it can -- it is probably constantly busy keeping the GUI responsive. In that case, as was mentioned in the comments on the question, it's better to just pack anything you want done after the worker's calculation is finished into a callback for the worker thread to call:

def my_calc(x, y):
    time.sleep(2)
    return x + y

def my_calc_thread(x, y, callback):
    z = my_calc(x, y)
    # depending on your GUI framework, you may need to do something
    # like call_in_main_thread(callback, z) if callback touches GUI
    # elements
    callback(z)

def example():
    def finish(z):
        print "result is {0}".format(z)
    t = threading.Thread(target=my_calc_thread, args=(2, 20, finish))
    t.start()

def runner():
    print "start"
    example()
1
votes

If you are using multi-threading, you should use futures to encapsulate your results. Specifically, the future.result(timeout=None) would be used in your situation:

Return the value returned by the call. If the call hasn’t yet completed then this method will wait up to timeout seconds. If the call hasn’t completed in timeout seconds, then a concurrent.futures.TimeoutError will be raised. timeout can be an int or float. If timeout is not specified or None, there is no limit to the wait time.

As mentioned in the comments above, if you are not using multi-threading (and are using asynchronous programming), then callbacks would be the way to go.