I have some generators doing some searching stuff, and I use another generator wrapping them up:
def searching_stuff_1():
# searching
yield 1
# and searching
yield 2
yield 3
def searching_stuff_2():
yield 4
yield 5
def gen():
yield from searching_stuff_1()
yield from searching_stuff_2()
for result in gen():
print(result)
So now I am wondering how I can rewrite it into async version, that can yield multiple values in searching_stuff_1 and searching_stuff_2.
I was trying:
import asyncio
async def searching_stuff_1():
f = asyncio.Future()
result = []
await asyncio.sleep(1)
#searching
result.append(1)
#searching
result.append(2)
result.append(3)
f.set_result(result)
return f
async def searching_stuff_2():
f = asyncio.Future()
result = []
await asyncio.sleep(1)
result.append(4)
result.append(5)
f.set_result(result)
return f
async def producer():
coros = [searching_stuff_1(), searching_stuff_2()]
for future in asyncio.as_completed(coros):
yield await future
async def consumer(xs):
async for future in xs:
for r in future.result():
print(r)
loop = asyncio.get_event_loop()
loop.run_until_complete(consumer(producer()))
loop.close()
However, in this version, I have to append all results into a list and wrap it in a Future instance. I am wondering if there is better way to handle multiple results from coroutine function. Is it possible that I can still yield those numbers?
The asyncio. gather() returns the results of awaitables as a tuple with the same order as you pass the awaitables to the function.
A coroutine is a function that can suspend its execution (yield) until the given YieldInstruction finishes.
Coroutines are Generators, but their yield accepts values. Coroutines can pause and resume execution (great for concurrency).
sleep(5) is called, it will block the entire execution of the script and it will be put on hold, just frozen, doing nothing. But when you call await asyncio. sleep(5) , it will ask the event loop to run something else while your await statement finishes its execution.
Yes you can still yield those numbers in async version, this is Asynchronous Generators, You can use async for(PEP492) and Asynchronous Comprehensions(PEP530), like this, rewrite from your first example. though this require python version higher than or equals to 3.6
import asyncio
async def searching_stuff_1():
# searching
yield 1
# and searching
yield 2
yield 3
async def searching_stuff_2():
yield 4
yield 5
async def gen():
async for i in searching_stuff_1():
yield i
async for i in searching_stuff_2():
yield i
async def gen_all():
return [i async for i in gen()]
if __name__ == "__main__":
result = asyncio.get_event_loop().run_until_complete(gen_all())
print(result)
for wishing to run two async generator async, you can use asyncio.gather
.
But since asyncio.gather
only collect coroutines' result in an async manner, you need to combine each yielded result from async generator separately with async def gen2
before calling asyncio.gather,
async def gen2(coro):
return [i async for i in coro()]
# combine two async
async def gen_all2():
return list(chain.from_iterable(await gather(gen2(searching_stuff_1), gen2(searching_stuff_2))))
To prove my point, we can change searching_stuff
to:
async def searching_stuff_1():
print("searching_stuff_1 begin")
# searching
yield 1
await asyncio.sleep(1)
# and searching
yield 2
yield 3
print("searching_stuff_1 end")
async def searching_stuff_2():
print("searching_stuff_2 begin")
yield 4
await asyncio.sleep(1)
yield 5
print("searching_stuff_2 end")
and then make the move:
result = asyncio.get_event_loop().run_until_complete(gen_all())
print(result)
> searching_stuff_2 begin
> searching_stuff_1 begin
> searching_stuff_2 end
> searching_stuff_1 end
> [1, 2, 3, 4, 5]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With