3

My code:

import asyncio
from random import randrange

async def inner_sleep(letter, num):
    print(f'start inner sleep {letter}, {num}')
    myint = randrange(5)
    await asyncio.sleep(myint)
    print(f'done with inner sleep {letter}, {num}')

async def outer_sleep(letter):
    print(f'start outer sleep {letter}')
    myint = randrange(5)
    await asyncio.sleep(myint)
    print(f'done with outer sleep {letter}')

async def inside(letter):
    nums = [1,2,3,4,5,6]
    tasks = []

    async def create_task(num):
        task = asyncio.ensure_future(inner_sleep(letter, num))
        tasks.append(task)
    
    for num in nums:
        await create_task(num)

    await asyncio.gather(*tasks)


async def outside():

    letters = ['a','b','c','d']
    tasks = []

    async def create_task_1(letter):
        task = asyncio.ensure_future(outer_sleep(letter))
        tasks.append(task)

    for letter in letters:
        await create_task_1(letter)
        await inside(letter)
    
    await asyncio.gather(*tasks)

asyncio.run(outside())

Sample output:

start outer sleep a
start inner sleep a, 1
start inner sleep a, 2
start inner sleep a, 3
start inner sleep a, 4
start inner sleep a, 5
start inner sleep a, 6
done with inner sleep a, 4
done with outer sleep a
done with inner sleep a, 2
done with inner sleep a, 3
done with inner sleep a, 1
done with inner sleep a, 5
done with inner sleep a, 6
start outer sleep b
start inner sleep b, 1
start inner sleep b, 2
start inner sleep b, 3
start inner sleep b, 4
start inner sleep b, 5
start inner sleep b, 6
done with inner sleep b, 3
done with inner sleep b, 5
done with inner sleep b, 4
done with outer sleep b
done with inner sleep b, 1
done with inner sleep b, 6
done with inner sleep b, 2
start outer sleep c
start inner sleep c, 1

I have a loop inside a loop and I want both of them to run asynchronously. The inner one works correctly, but I'm having trouble getting the outer one to do what I want. In the code above, I want my outer function to concurrently loop through the list 'letters'. For each letter, I need the first function (outer_sleep) to finish before the second function (inner_sleep) can start. I can't seem to find a place to put the inside function to accomplish this. When I run this code, you can see in the results how 'inner sleep a' starts before 'done with outer sleep a'. Ideally my output would look something like:

start outer sleep a
start outer sleep b
finish outer sleep a
start inner sleep a, 1
start inner sleep a, 2
start inner sleep a, 3
start inner sleep a, 4
start inner sleep a, 5
start inner sleep a, 6
finish outer sleep b
start inner sleep b, 1
start inner sleep b, 2
start inner sleep b, 3
start inner sleep b, 4
start inner sleep b, 5
start inner sleep b, 6
done with inner sleep a, 4
done with inner sleep a, 2
done with inner sleep b, 3

Is this possible?

2 Answers 2

2

What about just placing await inside(letter) at the end of outer_sleep? This ensures it will only be run once the result of that specific outer_sleep has arrived.

import asyncio
from random import randrange


async def inner_sleep(letter, num):
    print(f'start inner sleep {letter}, {num}')
    myint = randrange(5)
    await asyncio.sleep(myint)
    print(f'done with inner sleep {letter}, {num}')


async def inside(letter):
    nums = [1, 2, 3, 4, 5, 6]
    tasks = [asyncio.ensure_future(inner_sleep(letter, num)) for num in nums]
    await asyncio.gather(*tasks)


async def outer_sleep(letter):
    print(f'start outer sleep {letter}')
    myint = randrange(5)
    await asyncio.sleep(myint)
    print(f'done with outer sleep {letter}')
    await inside(letter)


async def outside():
    letters = ['a', 'b', 'c', 'd']
    tasks = [asyncio.ensure_future(outer_sleep(letter)) for letter in letters]
    await asyncio.gather(*tasks)


asyncio.run(outside())

The asyncio.gather means that both the numbers as well as the letters can be processed in any order (e.g. 'c' can come before 'b'), if that's okay with you.

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

Comments

1

In create_task_1 you're calling outer_sleep but you don't await the result. This means that the task is started but the execution of the outside function proceeds before it completes.

Is there a reason you need the create_task_1 function? Can you just await outer_sleep(letter) directly instead? For example (note this is untested):

async def outside():

    letters = ['a','b','c','d']
    
    # don't need create_task_1 funciton anymore

    for letter in letters:
        await outer_sleep(letter)
        await inside(letter)
    
    # don't need to gather the tasks as they're already complete

Edit:

You clarified in the comments that you want all of the outer_sleep tasks to start at the same time. You just need to flip around the order of execution a little

async def outside():

    letters = ['a','b','c','d']
    tasks = []

    async def task_1(letter):
        await outer_sleep(letter)
        await inside(letter)

    for letter in letters:
        task = asyncio.ensure_future(task_1(letter))
        tasks.append(task)
    
    await asyncio.gather(*tasks)

2 Comments

that does help ensure that outer_sleep finishes before inner_sleep, however it doesn't make the loop through letters asynchronous as well. i want to gather the whole loop through letters so that when it's awaiting the outer_sleep function for 'a', it can move on to start the outer_sleep for 'b'.
You can’t really achieve that with a for loop. You’ll need something like asyncio.gather.

Your Answer

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