user15373274
user15373274

Reputation:

python async function inside an async function

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?

Upvotes: 3

Views: 5005

Answers (2)

xjcl
xjcl

Reputation: 15309

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.

Upvotes: 2

Peter Gibson
Peter Gibson

Reputation: 19554

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)

Upvotes: 1

Related Questions