Frankie
Frankie

Reputation: 195

Pointer in while loop condition in Python

I need function_B to wait for function_A. The wait_for_action_to_be_done() function does the job but I would like something more convenient with wait_for(action_to_wait). The issue is that, in wait_for(action_to_wait) the action_to_wait variable in the condition in the while loop is never updated. I guess something like a pointer would help.

import asyncio

action_done = False

async def function_A(delay):
    global action_done
    while True:
        await asyncio.sleep(delay)
        action_done = True
        break

async def wait_for(action_to_wait):
    print("wait for action to be done...")
    while not action_to_wait:
        await asyncio.sleep(0.1)

async def wait_for_action_to_be_done():
    print("wait for action to be done...")
    while not action_done:
        await asyncio.sleep(0.1)

async def function_B():
    await wait_for_action_to_be_done() # OK, works
    # await wait_for(action_done) # KO, infinite while loop as the condition is never updated
    print("performing next action...")

async def main():
    await asyncio.gather(function_A(2), function_B())

asyncio.run(main())

Upvotes: 0

Views: 91

Answers (3)

Paul Cornelius
Paul Cornelius

Reputation: 10946

If function_b can't proceed until function_a finishes, why not just await function_a? Perhaps this is case of using gather() when you don't need it. This simple script expresses the design idea that function_b should not run until function_a finishes:

import asyncio

async def function_a(delay):
    await asyncio.sleep(delay)

async def function_b(delay):
    await function_a(delay)
    print("Next step")

async def main():
    await function_b(2)

asyncio.run(main())

You could also create a Task from function_a, pass the Task object to function_b and await it there.

Upvotes: 1

Ahmed AEK
Ahmed AEK

Reputation: 18090

async def wait_for(action_to_wait):
    print("wait for action to be done...")
    while not action_to_wait:
        await asyncio.sleep(0.1)

while not action_to_wait: waits for a local variable to change, it will never change. once you bind the global to a local variable, the local variable doesn't change when the global one does, see Facts and myths about Python names and values.

You need to wait for a non-local object to change, the easiest way is to use a lambda. (or nonlocal with an inline function).

async def wait_for(action_to_wait):
    print("wait for action to be done...")
    while not action_to_wait():
        await asyncio.sleep(0.1)

async def function_B():
    # await wait_for_action_to_be_done() # OK, works
    await wait_for(lambda: action_done)  # works!
    print("performing next action...")

Lastly using await asyncio.sleep(0.1) is fundamentally wrong, you should be using asyncio.Event instead.

import asyncio


async def function_A(delay, wait_event: asyncio.Event):
    while True:
        await asyncio.sleep(delay)
        wait_event.set()
        break

async def wait_for(wait_event: asyncio.Event):
    print("wait for action to be done...")
    await wait_event.wait()

async def function_B(wait_event: asyncio.Event):
    await wait_event.wait()  # works
    await wait_for(wait_event)  # also works
    print("performing next action...")

async def main():
    wait_event = asyncio.Event()
    await asyncio.gather(function_A(2, wait_event), function_B(wait_event))

asyncio.run(main())

The rule of thumb is that if you are using a sleep then you are likely doing something wrong, sleeps should only be used for inserting a real life delay or a timer, not waiting for things to happen.

If you are waiting for an event to happen from another thread you should be using a mixture of threading.Event and await loop.run_in_executor to wait for it, as asyncio.Event can only be used from inside the eventloop (not thread-safe).


note that you can simulate what you know about pointers in other languages using an object or a list or a dictionary or a dummy class.

class PackedBool:
    def __init__(self, value: bool):
        self.value = value

action_done = PackedBool(False)  # access using action_done.value

this is largely unnecessary for a bool where we already have the far superior events.

Upvotes: 3

Marce Puente
Marce Puente

Reputation: 515

For the condition to be updated, you can use a global variable or use as a while condition a call to the corresponding method

with global:

action_to_wait = True

async def wait_for():
    print("wait for action to be done...")
    global action_to_wait
    
    while action_to_wait:
        await asyncio.sleep( 0.1 )

    print("action done...")

with call:

state = False

def get_action_to_wait():
    return state

async def wait_for():
    print("wait for action to be done...")
    
    while get_action_to_wait():
        await asyncio.sleep( 0.1 )

    print("action done...")

Upvotes: 0

Related Questions