39

The following code outputs as follows:

1 sec delay, print "1", 
1 sec delay, print "2", 
1 sec delay, print "1", 
1 sec delay, print "2"

How can it be modified to run like this:

1 sec delay, print "1", print "1",
1 sec delay, print "2", print "2"

I would like it to run so that both instances of the for loop begin executing at the same time. As each instance executes, they will encounter the first() function at the same time, then the second() function at the same time, thus printing in the order mentioned above.

Code:

import asyncio

async def first():
    await asyncio.sleep(1)
    return "1"

async def second():
    await asyncio.sleep(1)
    return "2"

async def main():     
    for i in range(2):
      result = await first()
      print(result)
      result2 = await second()
      print(result2)


loop = asyncio.get_event_loop()
loop.run_until_complete(main())
1
  • 2
    There are several ways you could do what you're asking - are we assuming there are any constraints here, like that you can't edit the main function? For instance, you could put two loops in main that print the result of each function call twice, and it would produce the exact behavior you're asking for. However, again, if you're working under any constraints, we need to know them in order to help. Commented Nov 26, 2018 at 18:25

4 Answers 4

47

Looking at the desired output, it seems that the goal is to leave the individual iteration as it is - i.e. run first and second sequentially - but execute both loop iterations in parallel.

Assuming you only want to modify main(), it could be achieved like this:

async def main():
    async def one_iteration():
        result = await first()
        print(result)
        result2 = await second()
        print(result2)
    coros = [one_iteration() for _ in range(2)]
    await asyncio.gather(*coros)

Instead of iterating in sequence, the above creates a coroutine for each iteration task, and uses asyncio.gather to execute all the iterations in parallel.

Note that simply creating a coroutine doesn't start executing it, so a large number of coros won't block the event loop.

Sign up to request clarification or add additional context in comments.

6 Comments

This has no guarantee of printing out 1, 1, 2, 2. Though it may happen that way while the system waits for the I/O for printing and the sleep it is entirely possible that this prints 1, 2, 1, 2.
@NickChapman I don't see how it could print 1, 2, 1, 2 on a non-broken system. It basically executes asyncio.gather(one_iteration(), one_iteration()), and one_iteration sleeps for a second between the different prints executed sequentially. Since they are executed in parallel, one would expect 1, 1, 2, 2, with pauses between the 1s and the 2s.
It will most likely happen that way, but there is no guarantee depending on how constrained the system resources are. For whatever this person is doing it's probably fine for them, but people should be aware that this isn't bulletproof.
@NickChapman The order of output specified by the question clearly assumes a non-broken system, otherwise it wouldn't use sleeps to begin with. The answer shows how to execute loop iterations in parallel, which was the actual point.
@Media You can run it as shown in the question or using the more modern asyncio.run(main()).
|
7

To run the two functions simultaneously you can use gather. However, the results will be provided to you in the order you provide them. So for example if you do

results = await asyncio.gather(first(), second())

Then you will get [the result of first(), the result of second()] back. If you want to do something whenever each one returns then you should use Tasks explicitly and add callbacks.

4 Comments

@user4815162342 sure, though it won't solve the problem of getting the results back in the order you provided the functions rather than the order they actually execute in.
Looking more closely, this will result in 1 sec delay, print "1", print "2", repeated twice. The OP seems to want something different.
@user4815162342 ah yeah, ok it looks like they don't need asyncio at all in that case?
I believe the OP wants to leave the contents of the individual iteration as it is (i.e. run first and second in sequence), but execute all iterations in parallel. See my answer.
3

With the aysncio library you can use aysncio.gather()

loop.run_until_complete(asyncio.gather(
  first(),
  second()
))

This can come in handy if you are also sending HTTP requests in parallel:

loop.run_until_complete(asyncio.gather(
  request1(),
  request2()
))

Comments

0

In newer versions of python (3.10+?) I think you can do something like this:

import asyncio

async def async_generator():
    yield await first()
    yield await second()

async def main():
    async for item in async_generator():
        print(item)

asyncio.run(main())

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.