Reputation: 31
I have a function that looks for some information on different webpages and returns True if it finds it on the target webpage, otherwise it returns False. I want to call this function several times, each time for a different webpage, and I want each of them to run asynchronously, and I want this entire process to loop indefinitely until one of the instances of this function returns True. The entire code is very long so, I'm gonna simplify it below for the sake of getting the point across. This code works and does what I want, but I was wondering if there was a better or cleaner way of achieving the same thing. I'm very new to asyncio so take it easy on me :)
import asyncio
async def find_something(loop, item_to_find, website):
while True:
# Some code to check the contents of a webpage goes here
if item_to_find in website:
loop.stop()
return True
else:
return False
loop = asyncio.get_event_loop()
try:
asyncio.ensure_future(find_something(loop, item1, website1))
asyncio.ensure_future(find_something(loop, item2, website2))
asyncio.ensure_future(find_something(loop, item3, website3))
loop.run_forever()
except Exception as e:
pass
finally:
loop.close()
EDIT: I made a mistake in the code, as I did my original testing with print statements only and in this instance, returning False terminates the while loop and ends the function call automatically and so the function will not be looping indefinitely. So even more reasons for needing an alternative solution. I could just remove the while loop entirely and call the function inside of itself to make a recursive loop until the condition is met or I could just not return anything unless the desired condition is met, neither of these sound like a good solution though
Upvotes: 0
Views: 443
Reputation: 20709
I believe asyncio.as_completed
is what you're looking for. It will return results as each awaitable completes.
import asyncio
async def find_something(item_to_find, website):
contents = await some_code_to_check_the_contents_of_a_webpage(website)
return item_to_find in contents
async def main():
aws = [
find_something(item1, website1),
find_something(item2, website2),
find_something(item3, website3),
]
for coro in asyncio.as_completed(aws):
was_found = await coro
if was_found:
return
asyncio.run(main())
The one caveat is that there's no way to know which call to find_something
returned True
. If that matters, or if you need to cancel any pending tasks, you may want to switch from returning a boolean to returning some value that gives you the information you need (e.g., the arguments). You could then place the futures in a mapping and cancel any that you haven't already seen. This could look something like
aws = {
(item1, website1): asyncio.ensure_future(item1, website1),
(item2, website2): asyncio.ensure_future(item2, website2),
(item3, website3): asyncio.ensure_future(item3, website3),
}
for coro in asyncio.as_completed(aws.values()):
item, website, was_found = await coro
del aws[item, website]
if was_found:
for future in aws.values():
future.cancel()
return
Upvotes: 2