How to wait on multiple conditions simultaneously until any of them returns true

Question:

This is my code:

async def fun1():
   result=False
   #Some time-consuming operations...
   return result

async def fun2():
  result=False
  #Some time-consuming operations...
  return result

if fun1() or fun2():
  print("Success")

The if block runs all conditions by order, but I want run them simultaneously and wait until any of theme returns True. I know asyncio.wait(tasks,return_when=asyncio.FIRST_COMPLETED) but I don’t know how to use it for my purpose.

Asked By: Ayub

||

Answers:

You can use asyncio.ensure_future:

Example of code adapted from what you showed:

import asyncio, time, random 

async def fun1():
    result = False
    # Some time-consuming operations...
    result = random.randint(1, 10)
    await asyncio.sleep(result)
    return result

async def fun2():
    result = False
    # Some time-consuming operations...
    result = random.randint(1, 10)
    await asyncio.sleep(result)
    return result

async def main():
    t1 = asyncio.ensure_future(fun1())
    t2 = asyncio.ensure_future(fun2())

    count = 0
    while not any([t1.done(), t2.done()]):
        print(f'{count} - {t1.done()}, {t2.done()}')
        await asyncio.sleep(1)
        count += 1

    print(f'At least one task is done: {t1.done()}, {t2.done()}')

loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()

Only as a reminder that is possible for this code to trigger a message if the main ends before any task finish their execution. Example of a possible execution:

0 - False, False
1 - False, False
At least one task is done: False, True
Task was destroyed but it is pending!
task: <Task pending name='Task-2' coro=<fun1() running at /tmp:7> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x00000239C83B4B50>()]>>
Answered By: j3r3mias

Using your example code, the following will achieve what you want efficiently:

import asyncio

async def fun1():
   result=False
   #Some time-consuming operations...
   return result

async def fun2():
  result=False
  # This sleep means fun1 will finish first
  await asyncio.sleep(2)
  return result

result = False
done = []
pending = [asyncio.create_task(fun1()), asyncio.create_task(fun2())]

while len(pending) > 0:
    done, pending = await asyncio.wait(pending,return_when=asyncio.FIRST_COMPLETED)
    if any( t.result() == True for t in done ):
        result = True
        break

if result:        
    print("Success")
else:
    print("Failure")

The key thing to note here is that we have to keep evaluating asyncio.wait with the remaining pending list of tasks if we don’t see a success result. Note that ayncio.wait doesn’t return a coroutine directly but rather a task.

Implemented this way, the function will return as soon as any truthy value is.

Answered By: Will Rouesnel