Last Updated on November 8, 2022
You can create a task from a coroutine using the asyncio.create_task() function, or via low-level API functions such as asyncio.ensure_future() and loop.create_task().
In this tutorial, you will discover how to create an asyncio Task 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
You can learn more about asyncio tasks in the tutorial:
Now that we know what an asyncio task is, let’s look at how we might create one.
Run loops using all CPUs, download your FREE book to learn how.
How to Create a Task
Generally, we do not create an asyncio.Task object from a coroutine object directly.
Instead, we use a factory function that takes a coroutine and returns an asyncio.Task object.
A task can only be created from within a coroutine.
There are 3 main ways you can create an asyncio Task from a coroutine, they are:
- Create a Task with asyncio.create_task() (recommended)
- Create a Task with asyncio.ensure_future() (low-level)
- Create a Task with loop.create_task() (low-level)
Let’s take a closer look at each in turn:
Create a Task with asyncio.create_task()
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:
1 2 3 |
... # create and schedule a task task = asyncio.create_task(coro) |
We can specify a name for the task. This allows the same coroutine to be scheduled many times although each potentially with a different name, allowing them to be differentiated programmatically.
This can be achieved via the “name” argument.
For example:
1 2 3 |
... # create and schedule a task with a name task = asyncio.create_task(coro, name='MyTask') |
The asyncio.create_task() is a high-level asyncio API and is the preferred way to create Tasks in our asyncio programs.
Create a Task with asyncio.ensure_future()
We can create a task using the asyncio.ensure_future() function.
This function takes a Future, Task, Future-like object or a coroutine as an argument.
It will then schedule the task for execution and return a Task instance. If a coroutine is provided to the function, then it is wrapped for us in a Task instance, which is returned.
Return: a Task object wrapping obj, if obj is a coroutine (iscoroutine() is used for the test); in this case the coroutine will be scheduled by ensure_future().
— Futures
For example:
1 2 3 |
... # create and schedule a task task = asyncio.ensure_future(coro) |
The asyncio.ensure_future() allows the caller to specify the event loop used to schedule the task via the “loop” argument. By default, it will use the current event loop that is executing the coroutine that is creating the task.
This is a low-level asyncio API and is generally not recommended for creating tasks in asyncio programs.
Future objects are used to bridge low-level callback-based code with high-level async/await code.
— Futures
Create a Task with loop.create_task()
We can create tasks via the create_task() method on an event loop instance.
This requires that we first get an event loop instance, such as the currently running event loop. This can be achieved via the asyncio.get_event_loop() function.
For example:
1 2 3 |
... # get the current event loop loop = asyncio.get_event_loop() |
Once we have an event loop instance, we can call the create_task() method on the event loop and pass it a coroutine. It will then return a Task instance that wraps the provided coroutine and schedules it for execution.
Schedule the execution of coroutine coro. Return a Task object.
— Event Loop
For example:
1 2 3 |
... # create and schedule a task task = loop.create_task(coro) |
Like the asyncio.create_task() function, the loop.create_task() method takes a “name” argument that can be used to assign a logical name to the task.
For example:
1 2 3 |
... # create and schedule a task task = loop.create_task(coro, name='MyTask') |
Like the asyncio.ensure_future() function, the loop.create_task() method is a low-level asyncio API and is not the preferred way to create and schedule tasks in asyncio programs.
Application developers should typically use the high-level asyncio functions, such as asyncio.run(), and should rarely need to reference the loop object or call its methods. This section is intended mostly for authors of lower-level code, libraries, and frameworks, who need finer control over the event loop behavior.
— Event Loop
Now that we know how to create an asyncio task, let’s consider what creating a task actually does.
What Does Creating a Task Do?
Creating a task does a few things.
- It wraps the provided coroutine in an asyncio.Task instance.
- It schedules the task (wrapped coroutine) for execution in the event loop.
- It returns a Task instance that provides a handle on the scheduled coroutine.
The coroutine that is wrapped in the task is free to execute independently of the coroutine that created and scheduled it.
The wrapped coroutine does not run immediately.
Instead, it is scheduled and will run as soon as the event loop finds an opportunity to execute the task.
For example, it means that the caller that created and scheduled the task will block the task from executing until it terminates or suspends execution, e.g. by sleeping or awaiting the task.
Now that we know how to create a task, let’s look at some worked examples.
Free Python Asyncio Course
Download your FREE Asyncio PDF cheat sheet and get BONUS access to my free 7-day crash course on the Asyncio API.
Discover how to use the Python asyncio module including how to define, create, and run new coroutines and how to use non-blocking I/O.
Examples of Creating Asyncio Tasks
This section we will explore worked examples of the different ways we can create tasks in our asyncio programs.
Example of Creating a Task with asyncio.create_task()
We can explore how to create a task using the asyncio.create_task() function.
In this example, we will define a task coroutine that reports a message and sleeps for a moment.
The main coroutine will create a task from the task coroutine and then wait for the task to complete.
The complete example is listed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
# SuperFastPython.com # example of creating a task with asyncio.create_task() import asyncio # define a coroutine for a task async def task_coroutine(): # report a message print('executing the task') # block for a moment await asyncio.sleep(1) # custom coroutine async def main(): # report a message print('main coroutine started') # create and schedule the task task = asyncio.create_task(task_coroutine()) # wait for the task to complete await task # report a final message print('main coroutine done') # start the asyncio program asyncio.run(main()) |
Running the example first creates the main() coroutine and executes it as the entry point into the asyncio program.
The main() coroutine runs, first printing a message. It then creates an instance of the task_coroutine() coroutine and uses it to create a task.
This creates an asyncio.Task instance to wrap the coroutine, schedules it for execution and returns the task instance.
The main() coroutine receives the task instance, it is then suspended, awaiting the task to complete.
The task coroutine is given an opportunity to run, reporting a message then sleeping for a moment. It resumes and then terminates.
The main() coroutine resumes, reports a message, then terminates.
1 2 3 |
main coroutine started executing the task main coroutine done |
Next, let’s take a look at how we might create many tasks.
Example of Creating Many Tasks with asyncio.create_task()
We can explore how we might create many tasks from a coroutine.
In this example, we will update the task coroutine to take an integer argument that is then reported.
The main coroutine will create many Task objects from the task coroutine in a loop, using a list comprehension. This creates and schedules the tasks and creates a list of the returned task instances.
The complete example is listed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
# SuperFastPython.com # example of creating many tasks with asyncio.create_task() import asyncio # define a coroutine for a task async def task_coroutine(number): # report a message print(f'>executing the task {number}') # block for a moment await asyncio.sleep(1) # custom coroutine async def main(): # report a message print('main coroutine started') # create and schedule many tasks tasks = [asyncio.create_task(task_coroutine(i)) for i in range(20)] # wait for each task to complete for task in tasks: await task # report a final message print('main coroutine done') # start the asyncio program asyncio.run(main()) |
Running the example first creates the main() coroutine and executes it as the entry point into the asyncio program.
The main() coroutine runs and prints a message.
It then loops and creates 20 tasks in a list comprehension. This provides a unique integer to each coroutine as it is created, creates and schedules the coroutine for execution as a task, and collects the task instances into a list.
The main() coroutine then loops over the list of Task instances and awaits each in turn.
The task coroutines are given an opportunity to run, reporting a message with their integer argument then sleeping for a moment.
Only one task executes at a time and sequentially in order. This is because the tasks were scheduled sequentially and the event loop is honoring the scheduling order.
All tasks are completed and the main() coroutine resumes, reports a message, then terminates.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
main coroutine started >executing the task 0 >executing the task 1 >executing the task 2 >executing the task 3 >executing the task 4 >executing the task 5 >executing the task 6 >executing the task 7 >executing the task 8 >executing the task 9 >executing the task 10 >executing the task 11 >executing the task 12 >executing the task 13 >executing the task 14 >executing the task 15 >executing the task 16 >executing the task 17 >executing the task 18 >executing the task 19 main coroutine done |
Next, let’s look at the low-level Future API for creating a task.
Example of Creating a Task with asyncio.ensure_future()
We can explore how to create and schedule a task using the asyncio.ensure_future() function.
In this example, we will create the same task coroutine and pass it to the ensure_future() function from the main() coroutine in order to wrap it in a task and schedule it for execution.
The example is nearly identical to the example demonstrating the asyncio.create_task() function.
The complete example is listed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
# SuperFastPython.com # example of creating a task with asyncio.ensure_future() import asyncio # define a coroutine for a task async def task_coroutine(): # report a message print('executing the task') # block for a moment await asyncio.sleep(1) # custom coroutine async def main(): # report a message print('main coroutine started') # create and schedule the task task = asyncio.ensure_future(task_coroutine()) # wait for the task to complete await task # report a final message print('main coroutine done') # start the asyncio program asyncio.run(main()) |
Running the example first creates the main() coroutine and executes it as the entry point into the asyncio program.
The main() coroutine runs, first printing a message. It then creates an instance of the task_coroutine() coroutine and uses it to create a task via the ensure_future() function.
This creates an asyncio.Task instance to wrap the coroutine, schedules it for execution and returns the task instance.
The main() coroutine receives the task instance, it is then suspended, awaiting the task to complete.
The task coroutine is given an opportunity to run, reporting a message then sleeping for a moment. It resumes and then terminates.
The main() coroutine resumes, reports a message, then terminates.
1 2 3 |
main coroutine started executing the task main coroutine done |
Next, let’s look at the low-level event loop API for creating a task.
Example of Creating a Task with loop.create_task()
We can explore how to create a task using the event loop API with the loop.create_task() method.
In this example, we will update the above example to first get an instance for the current event loop, then use the event loop instance to create and schedule our task coroutine.
The complete example is listed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
# SuperFastPython.com # example of creating a task with loop.create_task() import asyncio # define a coroutine for a task async def task_coroutine(): # report a message print('executing the task') # block for a moment await asyncio.sleep(1) # custom coroutine async def main(): # report a message print('main coroutine started') # get the current event loop loop = asyncio.get_event_loop() # create and schedule the task task = loop.create_task(task_coroutine()) # wait for the task to complete await task # report a final message print('main coroutine done') # start the asyncio program asyncio.run(main()) |
Running the example first creates the main() coroutine and executes it as the entry point into the asyncio program.
The main() coroutine runs, first printing a message.
It then gets an instance of the current event loop. The event loop instance is then used to create and schedule the task coroutine as a task.
This creates an asyncio.Task instance to wrap the coroutine, schedules it for execution and returns the task instance.
The main() coroutine receives the task instance, it is then suspended, awaiting the task to complete.
The task coroutine is given an opportunity to run, reporting a message then sleeping for a moment. It resumes and then terminates.
The main() coroutine resumes, reports a message, then terminates.
1 2 3 |
main coroutine started executing the task main coroutine done |
Next, let’s take a look at what happens if we try to create a task from outside of a coroutine.
Overwhelmed by the python concurrency APIs?
Find relief, download my FREE Python Concurrency Mind Maps
Common Errors Creating a Task
In this section, we will look at common errors we may encounter when creating asyncio Tasks.
Create a Task Outside of a Coroutine
Generally, tasks can only be created from within coroutines.
If we attempt to create and schedule a task from outside of a coroutine, such as in the main program, then an error will be raised.
The example below demonstrates this.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
# SuperFastPython.com # example of attempting to create a task outside of a coroutine import asyncio # define a coroutine for a task async def task_coroutine(): # report a message print('executing the task') # block for a moment await asyncio.sleep(1) # custom coroutine async def main(): # report a message print('main coroutine started') # report a final message print('main coroutine done') # start the asyncio program asyncio.run(main()) # attempt to create a task task = asyncio.create_task(task_coroutine()) |
Running the example runs the main() coroutine as per normal.
The problem is that the asyncio.run() method blocks the thread. Once the asyncio program ends, the main thread attempts to create and schedule a task.
This fails because there is no currently running event loop.
1 2 3 4 5 6 |
main coroutine started main coroutine done Traceback (most recent call last): ... RuntimeError: no running event loop sys:1: RuntimeWarning: coroutine 'task_coroutine' was never awaited |
Next, let’s look at what happens if we try to schedule a function as a task.
Create a Task From a Function
Generally, a task is created from a coroutine object.
Recall a coroutine is defined using the “async def” expression, different from the “def” expression used to define a function.
We cannot create a task from a Python function. Attempting to do so, results in an error.
The example below demonstrates this.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
# SuperFastPython.com # example of attempting to create a task from a function import asyncio # define a coroutine for a task def task_coroutine(): # report a message print('executing the task') # custom coroutine async def main(): # report a message print('main coroutine started') # create and schedule the task task = asyncio.create_task(task_coroutine()) # wait for the task to complete await task # report a final message print('main coroutine done') # start the asyncio program asyncio.run(main()) |
Running the example starts the asyncio program using the main() coroutine as per normal.
It then attempts to create a task from task_coroutine() which is defined as a function, not a coroutine.
This results in a TypeError, as the asyncio framework is not able to operate upon the function (specifically the return value from the function) as though it were a coroutine.
1 2 3 4 5 |
main coroutine started executing the task Traceback (most recent call last): ... TypeError: a coroutine was expected, got None |
Takeaways
You now know how to create an asyncio Task in Python.
Do you have any questions?
Ask your questions in the comments below and I will do my best to answer.
Photo by Stefan Rodriguez on Unsplash
Do you have any questions?