What is an Asyncio Pending Task

November 18, 2022 Python Asyncio

A task that is scheduled or suspended will be assigned an internal state of "pending".

In this tutorial, you will discover pending asyncio tasks in Python.

Let's get started.

What is an Asyncio Task

An asyncio Task is an object that schedules and independently runs an asyncio coroutine.

It provides a handle on a scheduled coroutine that an asyncio program can query and use to interact with the coroutine.

A Task is an object that manages an independently running coroutine.

-- PEP 3156 – Asynchronous IO Support Rebooted: the "asyncio" Module

An asyncio task is represented via an instance of the asyncio.Task class.

A task is created from a coroutine. It requires a coroutine object, wraps the coroutine, schedules it for execution, and provides ways to interact with it.

A task is executed independently. This means it is scheduled in the asyncio event loop and will execute regardless of what else happens in the coroutine that created it. This is different from executing a coroutine directly, where the caller must wait for it to complete.

Tasks are used to schedule coroutines concurrently. When a coroutine is wrapped into a Task with functions like asyncio.create_task() the coroutine is automatically scheduled to run soon

-- Coroutines and Tasks

We can create a task using the asyncio.create_task() function.

This function takes a coroutine instance and an optional name for the task and returns an asyncio.Task instance.

Wrap the coro coroutine into a Task and schedule its execution. Return the Task object.

-- Coroutines and Tasks

For example:

...
# create and schedule a task
task = asyncio.create_task(coro)

You can learn more about asyncio tasks in the tutorial:

Now that we know about asyncio tasks, let's look at a pending task.

What is a Pending Task

A pending asyncio task is a task that is not currently running.

Technically, a pending task is a running task.

We can determine if a task is running by calling the done() method on an asyncio.Task object, which will return False.

For example:

...
# check if a task is running
if not task.done():
	# ...

While running, a task may be pending.

We cannot check if a task is pending directly. There is no method on an asyncio.Task object to check for this state.

Instead, we can print a Task object and the string representation of a task will indicate that it is "running" and potentially that it is "pending".

A task may not be pending for two main reasons, they are:

Let's take a closer look at these two cases.

Pending Scheduled Task

A scheduled task is a task that has been created and is scheduled in the event loop but has not yet had an opportunity to execute.

This will occur immediately after creating a task within another task or within a coroutine.

The new task will not run at least until the task or coroutine that created it is done or suspended.

For example:

...
# create and schedule a task
task = asyncio.create_task(...)
# ...
# the task is scheduled, but not yet executing

Technically, a scheduled task is running.

Because it is not running yet, it has the internal status of "pending".

Pending Running Task

A task can suspend itself.

This can be achieved if it awaits another task or coroutine.

This may be explicit via the await expression.

For example:

...
# suspend this running task
await asyncio.sleep(1)

It may be less obvious, such as if the task executes an asynchronous generator, iterator, or context manager.

For example:

...
# suspend this running task
async for item in items:
	# ...

When a task is suspended, it will be marked internally as "pending".

Now that we know about pending asyncio tasks, let's look at some examples.

Example of Pending Scheduled Task

A scheduled task has an internal state of pending.

We can explore this with a worked example.

In this example, we will create a new task from a custom coroutine that blocks for a moment. After the task is created and scheduled, before it begins executing, we will report its details.

The complete example is listed below.

# SuperFastPython.com
# example of a pending scheduled task
import asyncio

# define a coroutine for a task
async def task_coroutine():
    # block for a moment
    await asyncio.sleep(1)

# main coroutine
async def main():
    # create and schedule the task
    task = asyncio.create_task(task_coroutine())
    # report status
    print(task)
    # wait for the task to complete
    await task

# start the asyncio program
asyncio.run(main())

Running the example first creates the main coroutine and uses it to start the asyncio program.

The main() coroutine runs. It creates a new task coroutine and uses it to create and schedule a new task.

It then immediately reports the details of the task, before waiting for the task to be completed.

We can see that the task has an internal state of "pending" and "running".

<Task pending name='Task-2' coro=<task_coroutine() running at ...>>

Example of Pending Running Task

A suspended task has an internal state of pending.

We can explore this with a worked example.

In this example, we will create a new task from a custom coroutine that blocks for a moment. The main task will block for a moment to allow the task to begin executing. It will then resume and report the details of the suspended task.

The complete example is listed below.

# SuperFastPython.com
# example of a pending running task
import asyncio

# define a coroutine for a task
async def task_coroutine():
    # block for a moment
    await asyncio.sleep(1)

# main coroutine
async def main():
    # create and schedule the task
    task = asyncio.create_task(task_coroutine())
    # allow the task to run a moment
    await asyncio.sleep(0.5)
    # report status
    print(task)
    # wait for the task to complete
    await task

# start the asyncio program
asyncio.run(main())

Running the example first creates the main coroutine and uses it to start the asyncio program.

The main() coroutine runs. It creates a new task coroutine and uses it to create and schedule a new task.

The main() coroutine then suspends for a moment to allow the task to execute.

The task executes and immediately suspends itself with a call to sleep.

The main() coroutine resumes and reports the details of the task, before waiting for the task to be completed.

As with the scheduled task, we can see that the task has an internal state of "pending" and "running".

Unlike the scheduled task, we can see that the pending task is explicitly waiting to resume.

<Task pending name='Task-2' coro=<task_coroutine() running at ...> wait_for=<Future pending cb=[Task.task_wakeup()]>>

Takeaways

You now know about pending asyncio tasks in Python.