Artur
Artur

Reputation: 1065

Asynchronically wait for a method to complete in Python 3

Consider this simplified example:

def get_external_thing() -> str:
    my_variable = a_blocking_operation()
    my_variable.some_other_operation()
    return my_variable

externally_gathered_thing = None
while True:
    sleep(1)
    externally_gathered_thing = get_external_thing()
    if externally_gathered_thing:
        do_something_1()
        externally_gathered_thing = None
    do_something_2()

This will obviously enter the loop, sleep for a second, then go into get_external_thing() and wait until a_blocking_operation() is finished. Nothing else will be executed as long as get_external_thing() is running.

What I'm trying to accomplish is force my loop to continue if get_external_thing() is not finished and go straight to do_something_2(). However, if get_external_thing() is finished and externally_gathered_thing has some value, I want do_something_1() to be executed as well.

How can I accomplish it purely in Python? I tried to use this example to learn asyncio but didn't manage to produce any working example. Due to project requirements asyncio is a preferred but it's not a must.

In other words, I'd like do_something_2() to be executed every second (or every second + some small overhead), regardless of the results of get_external_thing().

Note: don't be scared by the while True construct, it's designed to run continuously on a raspberry pi :)

Upvotes: 3

Views: 79

Answers (1)

user4815162342
user4815162342

Reputation: 155505

For this kind of task look into the concurrent.futures module. For example:

def get_external_thing() -> str:
    my_variable = a_blocking_operation()
    my_variable.some_other_operation()
    return my_variable

externally_gathered_thing = None
executor = concurrent.futures.ThreadPoolExecutor()
working = None

while True:
    if working is None:
        # if no work is in progress, start the external task in a bg thread
        working = executor.submit(get_external_thing)
    try:
        # wait for the external result, but no more than a second
        externally_gathered_thing = working.result(timeout=1)
        working = None
    except concurrent.futures.TimeoutError:
        # in case of timeout, proceed with our logic anyway, we'll get
        # back to waiting in the next iteration
        pass

    if externally_gathered_thing is not None:
        do_something_1()
        externally_gathered_thing = None
    do_something_2()

An asyncio-based solution is possible, but it would still have to use threads under the hood to await a blocking operation (that's how run_in_executor works), so it would combine the complexity of asyncio with the complexity of threads.

Upvotes: 1

Related Questions