Luca
Luca

Reputation: 1810

Running code between task creation and await with asyncio

Using asyncio is it possible to create a task, then continue with the "main" code execution and await for the task results later on?

Consider the following code

from functools import reduce
import asyncio

async def a_task():
    print('a_task(): before sleep')
    # waiting for something to happen
    await asyncio.sleep(30)
    print('a_task(): after sleep')
    return 42

async def main():
    # Create a Task
    print('main() before create_task')
    task = asyncio.create_task(a_task())
    print('main() task created')

    print('Doing stuff here between task creation and await')
    # Computing 200000! should take few seconds... 
    # use a smaller number if its too slow on your machine
    x = reduce(lambda a,b: a*b, range(1, 200000))
    print('Stuff done')

    print('main() awaiting task')
    task_result = await task
    print('main() task awaited')
    return task_result

#%%
if __name__ == '__main__':
    results = asyncio.run(main())
    print(results)

This returns

main() before create_task
main() task created
Doing stuff here between task creation and await
Stuff done
main() awaiting task
a_task(): before sleep   <<---- Task only starts running here!!
a_task(): after sleep
main() task awaited
42

a_task is created before we start computing 200000!, but it's executed only when we call await task. Is it possible to make a_task start to run before we start computing 200000! and keep it running in the background?

I read the doc, this, this, etc... they all mention that tasks are what should be used to execute code in the background, but I can't understand how to run it without hanging the main code.

Upvotes: 2

Views: 1564

Answers (1)

Oleksandr Motornyi
Oleksandr Motornyi

Reputation: 38

I believe that the problem here is the following: creating a task with create_task does not schedule it for the immediate execution: it needs an await or something similar to trigger the switching of the event loop to start running something different. In your current code the creation of task is followed with synchronous code in which case the event loop cannot suspend the execution of main and start running the task. One way you can ensure that it would behave the way you expect would be putting asyncio.sleep(0) before running the evaluation of the factorial. That way while main() is being executed when it'll encounter await the event loop will suspend the execution of main and switch to a_task.

Other approach that can be interesting for you is using asyncio.gather docs link to schedule multiple async task and then to suspend the execution of main and wait for all of the tasks to complete.

from functools import reduce
import asyncio


async def a_task():
    print("a_task(): before sleep")
    # waiting for something to happen
    await asyncio.sleep(30)
    print("a_task(): after sleep")
    return 42


async def doing_something_else():
    print("We start doing something else")
    x = reduce(lambda a, b: a * b, range(1, 200000))
    print("We finish doing something else")


async def main():
    # Create a Task
    print("main() before create_task")
    task = asyncio.create_task(a_task())
    print("main() task created")
    print("main() before create_task 2")
    task_2 = asyncio.create_task(doing_something_else())
    print("main() task 2 created")

    print("main() gathering tasks")
    # task_result = await task
    await asyncio.gather(task, task_2)
    print("main() tasks finished")
    # return task_result


#%%
if __name__ == "__main__":
    results = asyncio.run(main())
    print(results)

Upvotes: 1

Related Questions