What is the Main Coroutine

February 1, 2024 Python Asyncio

The coroutine provided to asyncio.run() to start the asyncio event loop is called the main coroutine or the main task.

It has special properties, such as when it is done, the asyncio event loop is shut down and all other tasks are canceled.

In this tutorial, you will discover the main coroutine in asyncio programs.

Let's get started.

What is the Main Coroutine

A coroutine is a function that can be suspended and resumed.

It is often defined as a generalized subroutine.

A subroutine can be executed, starting at one point and finishing at another point. Whereas, a coroutine can be executed then suspended and resumed many times before finally terminating.

You can learn more about Python coroutines in the tutorial:

An asyncio program is started explicitly by starting the event loop.

Typically, this involves calling the asyncio.run() module function and providing a coroutine object to execute as the entry point to the program.

For example:

...
# start the event loop
asyncio.run(main())

The coroutine that is provided to asyncio.run() is called the main coroutine.

Note, this does not refer to coroutines executed via the newer asyncio.Runner context manager that is able to execute an arbitrary number of coroutines in the event loop and there is no main coroutine.

You can learn more about the asyncio.Runner context manager in the tutorial:

The asyncio documentation sometimes refers to the main coroutine as the "main task":

For example:

The Runner creates the main task for the passed coroutine for its execution.

-- Asyncio Runners

The main coroutine is synonymous with the "main thread" and the "main process".

Recall that the main thread is the entry point into a program or the default thread, and has the name "MainThread".

You can learn more about the main thread in the tutorial:

Also, recall that the main process is the process that encloses the main thread and is the first or main parent process created when the Python interpreter is started and has the name "MainProcess".

You can learn more about the main process in the tutorial:

Both the main thread and the main process have special properties, and so does the main coroutine.

Special Properties of the Main Coroutine

The main coroutine has special properties compared to other asyncio tasks and coroutines.

  1. The main coroutine is the entry point into the asyncio event loop.
  2. When the main coroutine exits, all other tasks are canceled and the asyncio event loop is terminated.
  3. When a SIGINT is raised in the program the main task is called via the cancel() method.

Some differences between the main thread and the main process include:

The Most Important Property of the Main Coroutine

The most important detail of the main coroutine is that when it is done, the event loop is closed.

Recall, that the main coroutine can be done in a few ways:

Once done, the asyncio event loop is terminated.

As part of the shutdown, all other tasks in the event loop are canceled.

This is important because if we want to immediately shut down the event loop on demand, we must do so by completing the main coroutine.

You can learn more about this in the tutorial:

Now that we are familiar with the main coroutine, let's look at a worked example.

Example of Reporting Details of Main Coroutine

We can explore an example of reporting the details of the main coroutine.

In this example, we will define a main coroutine that gets the asyncio.Task object for the current task and reports its type and details.

We expect that it is not special or different from a normal asyncio task, e.g. unlike how the main thread and main process are different from normal thread and process objects.

Will then await a second work coroutine that reports the same details.

The complete example is listed below.

# SuperFastPython.com
# report the details of the main coroutine
import asyncio

# function to report task details
def report_task_details():
    # get the current task
    task = asyncio.current_task()
    # report task type
    print(type(task))
    # report task details
    print(task)

# another coroutine
async def work():
    # report the task details
    report_task_details()

# main coroutine
async def main():
    # report the task details
    report_task_details()
    # run the other coroutine
    await asyncio.create_task(work())

# start the asyncio event loop
asyncio.run(main())

Running the example starts the asyncio event loop and runs the main() coroutine.

Our main() coroutine is taken as the "main coroutine" of the program.

The main() coroutine runs and calls our report_task_details() function to report task details. The function reports the data type for the main() coroutine, and then reports the details of the task object.

Next, the work() coroutine is scheduled as a new asyncio.Task and awaited by the main coroutine.

The work() task calls our report_task_details() function and reports the type and details of itself.

We can see that both the main coroutine and the new work() task have the same data type of "_asyncio.Task".

The results highlight that the asyncio.Task type for the main coroutine is not special or different from a normal asyncio task.

<class '_asyncio.Task'>
<Task pending name='Task-1' coro=<main() running at ...:22> cb=[_run_until_complete_cb() at .../asyncio/base_events.py:180]>
<class '_asyncio.Task'>
<Task pending name='Task-2' coro=<work() running at ...:17> cb=[Task.task_wakeup()]>

Takeaways

You now know about the main coroutine in asyncio programs.