Patrick Collins
Patrick Collins

Reputation: 6131

Why do I need to wrap a python coroutine into a task/when to use a task or a coroutine?

In python, there are 3 main types awaitable objects: coroutines, Tasks, and Futures.

I can await a coroutine, and also a tasks.

Awaiting a coroutine

import asyncio

async def nested():
    return 42

async def main():
    print(await nested())  # will print "42".

asyncio.run(main())

Awaiting a task

import asyncio

async def nested():
    return 42

async def main():
    task = asyncio.create_task(nested())
    await task

asyncio.run(main())

What is the value of wrapping the coroutine in a task in the first place? It looks like they do the same thing.

When would I need to use a task vs a coroutine?

Upvotes: 8

Views: 3039

Answers (3)

Shadows In Rain
Shadows In Rain

Reputation: 1220

Coroutine is just a function that runs in the context of current awaitable. It can yield execution to the event loop on behalf of the caller (the one who calls await). Think of a function that is allowed to pause it's thread. You can call one coroutine from another, but they still share the same thread.

Task, on other hand, immediately posts a separate job to an event loop. The task itself is a handle to that job. You may await a task, but it can run on itself just fine in "parallel" — in single threaded context this means that task can run while other josb are yielding (e.g. waiting for the I/O). Task may complete even before you call await.

Example without tasks:

job_1 = sleep(5)
job_2 = sleep(2)

# will sleep for 5 seconds
await job_1

# will sleep for another 2 seconds
await job_2

Example with tasks:

job_1 = sleep(5)
job_2 = asyncio.create_task(sleep(2))

# will sleep for 5 seconds
await job_1

# by this time, job_2 is complete
# because previous job has yielded at some point, allowing other jobs to run
# thus await takes no time
await job_2

Upvotes: 13

Masklinn
Masklinn

Reputation: 42302

In this case there's no real difference: by awaiting the coroutine it's going to get scheduled as part of the task it's part of. However that means it's driven by its parent.

By wrapping a coroutine in a task, it gets independently scheduled on the event loop, meaning it is not driven by the containing task anymore (it has its own lifecycle) and it can be interacted with more richly (e.g. cancelled or have callbacks added to it).

Think "function" versus "thread", really. A coroutine is just a function which can be suspended (if it awaits stuff), but it still only exists within the lexical and dynamic context of its caller. A task is freed from that context, it makes the wrapped coroutine live its own life in the same way a thread makes the wrapped function (target) live its own life.

Upvotes: 6

Caleth
Caleth

Reputation: 62719

Creating a Task schedules the passed coroutine to be run on an event loop. You can use the Task to cancel the underlying coroutine.

Upvotes: 3

Related Questions