Last Updated on November 14, 2023
An asyncio task is a scheduled and independently managed coroutine.
Asyncio tasks provide a handle on independently scheduled and running coroutines and allow the task to be queried, canceled, and results and exceptions to be retrieved later.
We can create asyncio.Task objects from coroutines in asyncio programs.
In this tutorial, you will discover how to create and use asyncio tasks.
After completing this tutorial, you will know:
- How to create an asyncio task using a coroutine.
- Understand what creating a task does and when an asyncio task runs.
- How to schedule and execute an asyncio task to run in the background.
Let’s get started.
What is an Asyncio Task
A 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
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
The asyncio.Task class extends the asyncio.Future class and an instance are awaitable.
A Future is a lower-level class that represents a result that will eventually arrive.
A Future is a special low-level awaitable object that represents an eventual result of an asynchronous operation.
— Coroutines and Tasks
Classes that extend the Future class are often referred to as Future-like.
A Future-like object that runs a Python coroutine.
— Coroutines and Tasks
Because a Task is awaitable it means that a coroutine can wait for a task to be done using the await expression.
For example:
1 2 3 |
... # wait for a task to be done await task |
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
A task is created using a provided coroutine instance.
Recall that a coroutine is defined using the async def expression and looks like a function.
For example:
1 2 3 |
# define a coroutine async def task_coroutine(): # ... |
A task can only be created and scheduled within a coroutine.
There are two main ways to create and schedule a task, they are:
- Create Task With High-Level API (preferred)
- Create Task With Low-Level API
Let’s take a closer look at each in turn.
Create Task With High-Level API
A task can be created using the asyncio.create_task() function.
The asyncio.create_task() function takes a coroutine instance and an optional name for the task and returns an asyncio.Task instance.
For example:
1 2 3 4 5 |
... # create a coroutine coro = task_coroutine() # create a task from a coroutine task = asyncio.create_task(coro) |
This can be achieved with a compound statement on a single line.
For example:
1 2 3 |
... # create a task from a coroutine task = asyncio.create_task(task_coroutine()) |
This will do a few things:
- Wrap the coroutine in a Task instance.
- Schedule the task for execution in the current event loop.
- Return a Task instance
The task instance can be discarded, interacted with via methods, and awaited by a coroutine.
This is the preferred way to create a Task from a coroutine in an asyncio program.
Create Task With Low-Level API
A task can also be created from a coroutine using the lower-level asyncio API.
The first way is to use the asyncio.ensure_future() function.
This function takes a Task, Future, or Future-like object, such as a coroutine, and optionally the loop in which to schedule it.
If a loop is not provided, it will be scheduled in the current event loop.
If a coroutine is provided to this function, it is wrapped in a Task instance for us, which is returned.
For example:
1 2 3 |
... # create and schedule the task task = asyncio.ensure_future(task_coroutine()) |
Another low-level function that we can use to create and schedule a Task is the loop.create_task() method.
This function requires access to a specific event loop in which to execute the coroutine as a task.
We can acquire an instance to the current event loop within an asyncio program via the asyncio.get_event_loop() function.
This can then be used to call the create_task() method to create a Task instance and schedule it for execution.
For example:
1 2 3 4 5 |
... # get the current event loop loop = asyncio.get_event_loop() # create and schedule the task task = loop.create_task(task_coroutine()) |
When Does a Task Run?
A common question after creating a task is when does it run?
This is a good question.
Although we can schedule a coroutine to run independently as a task with the create_task() function, it may not run immediately.
In fact, the task will not execute until the event loop has an opportunity to run.
This will not happen until all other coroutines are not running and it is the task’s turn to run.
For example, if we had an asyncio program with one coroutine that created and scheduled a task, the scheduled task will not run until the calling coroutine that created the task is suspended.
This may happen if the calling coroutine chooses to sleep, chooses to await another coroutine or task, or chooses to await the new task that was scheduled.
For example:
1 2 3 4 5 |
... # create a task from a coroutine task = asyncio.create_task(task_coroutine()) # await the task, allowing it to run await task |
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.
Can We Run The Task in the Background
Yes.
A created and scheduled task will run in the background by default.
It is independent of the coroutine that created and scheduled it.
This means that you do not need to keep a reference to it in the coroutine that created it.
It also means that the coroutine that created it does not have to await it.
For example, we can ignore the returned task instance:
1 2 3 |
... # create a task from a coroutine _ = asyncio.create_task(task_coroutine()) |
Or, alternately:
1 2 3 |
... # create a task from a coroutine asyncio.create_task(task_coroutine()) |
These calls do not block and allow the Task instance that is returned to be ignored and the calling coroutine to continue executing.
Importantly, we should keep a reference to the scheduled task somewhere.
This is to avoid the task from being garbage collected.
Save a reference to the result of this function, to avoid a task disappearing mid execution. The event loop only keeps weak references to tasks. A task that isn’t referenced elsewhere may get garbage-collected at any time, even before it’s done.
— Coroutines and Tasks
The Task API documentation suggests storing background tasks in a global collection, such as a set, and removing them from the set once the task is done, such as via a done-callback function.
Overwhelmed by the python concurrency APIs?
Find relief, download my FREE Python Concurrency Mind Maps
How to Check Task Status
After a Task is created, we can check the status of the task.
There are two statuses we might want to check, they are:
- Whether the task is done.
- Whether the task was canceled.
Let’s take a closer look at each in turn.
Check if a Task is Done
We can check if a task is done via the done() method.
The method returns True if the task is done, or False otherwise.
For example:
1 2 3 4 |
... # check if a task is done if task.done(): # ... |
A task is done if it has had the opportunity to run and is now no longer running.
A task that has been scheduled is not done.
Similarly, a task that is running is not done.
A task is done if:
- The coroutine finishes normally.
- The coroutine returns explicitly.
- An unexpected error or exception is raised in the coroutine
- The task is canceled.
Check if a Task is Canceled
We can check if a task is canceled via the cancelled() method.
The method returns True if the task was canceled, or False otherwise.
For example:
1 2 3 4 |
... # check if a task was canceled if task.cancelled(): # ... |
A task is canceled if the cancel() method was called on the task and completed successfully, e..g cancel() returned True.
A task is not canceled if the cancel() method was not called, or if the cancel() method was called but failed to cancel the task.
How to Get Task Result
We can get the result of a task via the result() method.
This returns the return value of the coroutine wrapped by the Task or None if the wrapped coroutine does not explicitly return a value.
For example:
1 2 3 |
... # get the return value from the wrapped coroutine value = task.result() |
If the coroutine raises an unhandled error or exception, it is re-raised when calling the result() method and may need to be handled.
For example:
1 2 3 4 5 6 |
... try: # get the return value from the wrapped coroutine value = task.result() except Exception: # task failed and there is no result |
If the task was canceled, then a CancelledError exception is raised when calling the result() method and may need to be handled.
For example:
1 2 3 4 5 6 |
... try: # get the return value from the wrapped coroutine value = task.result() except asyncio.CancelledError: # task was canceled |
As such, it is a good idea to check if the task was canceled first.
For example:
1 2 3 4 5 6 7 |
... # check if the task was not canceled if not task.cancelled(): # get the return value from the wrapped coroutine value = task.result() else: # task was canceled |
If the task is not yet done, then an InvalidStateError exception is raised when calling the result() method and may need to be handled.
For example:
1 2 3 4 5 6 |
... try: # get the return value from the wrapped coroutine value = task.result() except asyncio.InvalidStateError: # task is not yet done |
As such, it is a good idea to check if the task is done first.
For example:
1 2 3 4 5 6 |
... # check if the task is not done if not task.done(): await task # get the return value from the wrapped coroutine value = task.result() |
How to Get Task Exception
A coroutine wrapped by a task may raise an exception that is not handled.
This will cancel the task, in effect.
We can retrieve an unhandled exception in the coroutine wrapped by a task via the exception() method.
For example:
1 2 3 |
... # get the exception raised by a task exception = task.exception() |
If an unhandled exception was not raised in the wrapped coroutine, then a value of None is returned.
If the task was canceled, then a CancelledError exception is raised when calling the exception() method and may need to be handled.
For example:
1 2 3 4 5 6 |
... try: # get the exception raised by a task exception = task.exception() except asyncio.CancelledError: # task was canceled |
As such, it is a good idea to check if the task was canceled first.
For example:
1 2 3 4 5 6 7 |
... # check if the task was not canceled if not task.cancelled(): # get the exception raised by a task exception = task.exception() else: # task was canceled |
If the task is not yet done, then an InvalidStateError exception is raised when calling the exception() method and may need to be handled.
For example:
1 2 3 4 5 6 |
... try: # get the exception raised by a task exception = task.exception() except asyncio.InvalidStateError: # task is not yet done |
As such, it is a good idea to check if the task is done first.
For example:
1 2 3 4 5 6 |
... # check if the task is not done if not task.done(): await task # get the exception raised by a task exception = task.exception() |
How to Cancel a Task
We can cancel a scheduled task via the cancel() method.
The cancel method returns True if the task was canceled, or False otherwise.
For example:
1 2 3 |
... # cancel the task was_cancelled = task.cancel() |
If the task is already done, it cannot be canceled and the cancel() method will return False and the task will not have the status of canceled.
The next time the task is given an opportunity to run, it will raise a CancelledError exception.
If the CancelledError exception is not handled within the wrapped coroutine, the task will be canceled.
Otherwise, if the CancelledError exception is handled within the wrapped coroutine, the task will not be canceled.
The cancel() method can also take a message argument which will be used in the content of the CancelledError.
How to Use Callback With a Task
We can add a done callback function to a task via the add_done_callback() method.
This method takes the name of a function to call when the task is done.
The callback function must take the Task instance as an argument.
For example:
1 2 3 4 5 6 7 |
# done callback function def handle(task): print(task) ... # register a done callback function task.add_done_callback(handle) |
Recall that a task may be done when the wrapped coroutine finishes normally when it returns, when an unhandled exception is raised or when the task is canceled.
The add_done_callback() method can be used to add or register as many done callback functions as we like.
We can also remove or de-register a callback function via the remove_done_callback() function.
For example:
1 2 3 |
... # remove a done callback function task.remove_done_callback(handle) |
How to Set the Task Name
A task may have a name.
This name can be helpful if multiple tasks are created from the same coroutine and we need some way to tell them apart programmatically.
The name can be set when the task is created from a coroutine via the “name” argument.
For example:
1 2 3 |
... # create a task from a coroutine task = asyncio.create_task(task_coroutine(), name='MyTask') |
The name for the task can also be set via the set_name() method.
For example:
1 2 3 |
... # set the name of the task task.set_name('MyTask') |
We can retrieve the name of a task via the get_name() method.
For example:
1 2 3 |
... # get the name of a task name = task.get_name() |
Now that we are familiar with how to create and use a task, let’s look at some worked examples.
Example of Creating and Using a Task
We can explore an example of creating a Task from a coroutine in an asyncio program.
This is the quintessential use case of creating and using a task.
In this example, we will define a coroutine that we will wrap in a task. We will then define the main coroutine that will be the entry point of the program. A task will be created from our task coroutine and then await 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 |
# SuperFastPython.com # example of creating and awaiting an asyncio 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') # create and schedule the task task = asyncio.create_task(task_coroutine()) # 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 as the entry point to the asyncio program.
The main() coroutine then reports a message. It then creates an instance of the task_coroutine() routine and passes it to the create_task() method in order to wrap it in a Task instance and return the instance.
This schedules the Task-wrapped coroutine for execution as soon as it is able.
The main() coroutine then continues on and then suspends execution and awaits the task to be completed.
This gives the task an opportunity to execute. It reports a message and then suspends, sleeping for one second.
At this time both the main() coroutine and the Task are suspended.
The Task resumes and then terminates.
The main() coroutine then continues on and terminates, which closes the asyncio program.
1 2 |
main coroutine executing the task |
Next, let’s explore how we might check the status of a task.
Example of Checking the Status of a Task
We can explore how to check the status of a scheduled task.
In this example, we will schedule a task and then immediately check if it is done or canceled. The calling coroutine will then await the task, then once the task is one, it will again check and report its stats.
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 checking the status of an asyncio 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(): # create and schedule the task task = asyncio.create_task(task_coroutine()) # report task status print(f'>done: {task.done()}') print(f'>canceled: {task.cancelled()}') # wait for the task to complete await task # report task status again print(f'>done: {task.done()}') print(f'>canceled: {task.cancelled()}') # start the asyncio program asyncio.run(main()) |
Running the example first creates the main() coroutine and uses it as the entry point to the asyncio program.
The main() coroutine then creates the task_coroutine() and schedules it as an independent task.
The coroutine then reports the status of the task, reporting whether it is done and whether it is canceled.
The task has not yet had an opportunity to execute and is marked not done. We have not canceled it, so it remained uncancelled.
The main() coroutine then awaits the task. This gives the task an opportunity to execute, reporting a message and blocking for a moment to simulate work.
The task finishes and the main() coroutine resumes. It reports the done and canceled status of the task again.
This time, as expected, the task is marked as done and remains not canceled (because we did not cancel it).
1 2 3 4 5 |
>done: False >canceled: False executing the task >done: True >canceled: False |
Next, let’s explore how we might get the result from a task.
Example of Getting the Result from a Task
We can explore how to get the result from a Task.
Recall, that a result refers to the return value from the coroutine wrapped by the task.
In this example, we define a task that returns an arbitrary value when it is done. The coroutine is wrapped in a task and awaited. Once the task is done, the caller retrieves the result and reports its value.
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 27 |
# SuperFastPython.com # example of getting the result from an asyncio 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) # return a value return 99 # custom coroutine async def main(): # report a message print('main coroutine') # create and schedule the task task = asyncio.create_task(task_coroutine()) # wait for the task to complete await task # get the result result = task.result() print(f'>got: {result}') # start the asyncio program asyncio.run(main()) |
Running the example first creates the main() coroutine and uses it as the entry point to the asyncio program.
The main() coroutine then reports a message. It then creates an instance of the task_coroutine() routine and passes it to the create_task() method in order to wrap it in a Task instance and return the instance.
This schedules the Task-wrapped coroutine for execution as soon as it is able.
The main() coroutine then continues on and then suspends execution and awaits the task to be completed.
This gives the task an opportunity to execute. It reports a message and then suspends, sleeping for one second. Once it resumes, it returns an integer value.
The main() coroutine then resumes. At this point, we know that the task is complete.
The main() coroutine retrieves the result from the task and reports it directly.
1 2 3 |
main coroutine executing the task >got: 99 |
Next, let’s explore getting an unhandled exception from a task.
Example of Getting an Exception from a Task
We can explore how we might get an unhandled exception from a task raised in the wrapped coroutine.
In this example, we will contrive a coroutine to wrap in a task that arbitrarily raises an Exception. The calling coroutine will schedule the task, wait for it to be done, then retrieve the exception that is expected to have been raised.
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 27 28 |
# SuperFastPython.com # example of getting an exception from an asyncio 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) # raise an exception raise Exception('Something bad happened') # custom coroutine async def main(): # report a message print('main coroutine') # create and schedule the task task = asyncio.create_task(task_coroutine()) # wait for the task to complete await asyncio.sleep(1.1) # get the exception exception = task.exception() # report the exception print(f'>got: {exception}') # start the asyncio program asyncio.run(main()) |
Running the example first creates the main() coroutine and uses it as the entry point to the asyncio program.
The main() coroutine then reports a message. It then creates an instance of the task_coroutine() routine and passes it to the create_task() method in order to wrap it in a Task instance and return the instance.
The main() coroutine then suspends execution and awaits the task to complete.
This gives the task an opportunity to execute. It reports a message and then suspends, sleeping for one second. Once it resumes it raises an exception.
The exception is caught by the asyncio runtime and stored for later access. It does not break the event loop.
The task is now done.
The main() coroutine then resumes. At this point, we know that the task is complete.
The main() coroutine retrieves the expected Exception from the task and reports it directly.
1 2 3 |
main coroutine executing the task >got: Something bad happened |
Next, let’s look at how we might cancel a task.
Example of Canceling a Task
We can explore how to cancel a task.
In this example, we will create and schedule a task as per normal. The caller will then wait a moment and allow the task to begin executing.
It will then cancel the task and check that the request to cancel was successful.
The caller will then wait a moment more for the task to be canceled, then report the status of the task to confirm it is marked as canceled.
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 27 28 29 |
# SuperFastPython.com # example of canceling an asyncio 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') # create and schedule the task task = asyncio.create_task(task_coroutine()) # wait a moment await asyncio.sleep(0.5) # cancel the task was_cancelled = task.cancel() print(f'>was canceled: {was_cancelled}') # wait a moment await asyncio.sleep(0.1) # report the status print(f'>canceled: {task.cancelled()}') # start the asyncio program asyncio.run(main()) |
Running the example first creates the main() coroutine and uses it as the entry point to the asyncio program.
The main() coroutine then reports a message. It then creates an instance of the task_coroutine() routine and passes it to the create_task() method in order to wrap it in a Task instance and return the instance.
The main() coroutine then suspends execution and blocks for half a second.
This gives the task an opportunity to execute the task. The task reports a message and then suspends, sleeping for one second.
The main() coroutine resumes and cancels the task. It then reports whether the request to cancel the task was successful. It was because we know that the task is not yet done. The main() coroutine then suspends for a fraction of a second.
This gives the task another opportunity to execute, in which case the CancelledError exception is raised in the wrapped coroutine, canceling the task.
The main() coroutine then resumes and checks the canceled status of the task, confirming that it indeed is done and was canceled.
1 2 3 4 |
main coroutine executing the task >was canceled: True >canceled: True |
Next, let’s look at how we might use a done callback function.
Example of Task Done Callback Function
We can explore how to use a done callback function on a task.
In this example, we will define a done callback function that will report whether a task is done or not.
The function will then be registered on the task after it is created.
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 27 28 |
# SuperFastPython.com # example of adding a done callback function to an asyncio task import asyncio # custom done callback function def handle(task): print(f'Task callback done: {task.done()}') # 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') # create and schedule the task task = asyncio.create_task(task_coroutine()) # add a done callback function task.add_done_callback(handle) # 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 as the entry point to the asyncio program.
The main() coroutine then reports a message. It then creates an instance of the task_coroutine() routine and passes it to the create_task() method in order to wrap it in a Task instance and return the instance.
The main() coroutine then registers the done callback function on the task. It then suspends execution and awaits the task to be completed.
This gives the task an opportunity to execute. It reports a message and then suspends, sleeping for one second. It resumes and terminates.
This triggers the asyncio infrastructure to call the callback function and pass it the Task instance.
The callback function is executed and reports a message, confirming that indeed the task is marked as done.
1 2 3 |
main coroutine executing the task Task callback done: True |
Next, let’s look at how we might set a name for a task.
Example of Setting the Task Name
We can explore how to assign and retrieve the name for an asyncio task.
In this example, we will create the task and assign it a name. Once scheduled, we will then report the name of the task to confirm it is assigned correctly.
Once the task is done, the name of the task is changed, and the name is then retrieved to confirm the change was made as expected.
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 27 28 29 |
# SuperFastPython.com # example using the name of an asyncio 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') # create and schedule the task task = asyncio.create_task(task_coroutine(), name='MyTask') # report the name of the task print(f'Task name: {task.get_name()}') # wait for the task to complete await task # change the name task.set_name('MyTaskDone') # report the name of the task again print(f'Task name: {task.get_name()}') print(task) # start the asyncio program asyncio.run(main()) |
Running the example first creates the main() coroutine and uses it as the entry point to the asyncio program.
The main() coroutine then reports a message. It then creates an instance of the task_coroutine() routine and passes it to the create_task() method in order to wrap it in a Task instance and return the instance. It also specifies the name of the task via the “name” argument.
The main() coroutine then retrieves the name of the task and reports it, confirming that the name that was assigned when the task was created, was assigned as expected.
The main() coroutine then suspends execution and awaits the task to complete.
This gives the task an opportunity to execute. It reports a message and then suspends, sleeping for one second. It resumes and terminates.
The main() coroutine resumes and changes the name of the task. The task name is retrieved again and reported, confirming that the name can be changed at arbitrary times, such as after the task is done.
Finally, the Task object is printed.
This shows the status of the task, but also includes the name that was assigned to the task.
1 2 3 4 5 |
main coroutine Task name: MyTask executing the task Task name: MyTaskDone <Task finished name='MyTaskDone' coro=<task_coroutine() done, defined at ...> result=None> |
Next, let’s look at some common errors when creating asyncio tasks.
Common Errors with Tasks
This section explores some common errors when creating and using asyncio tasks.
Example of Task Wrapping a Function
We cannot create a Task from a Python function.
The asyncio.create_task() method expects an instance of a coroutine, defined via the “async def” expression.
If a function is provided instead, an error will be raised.
This is a common error made either accidentally when typing, or by beginners.
The example below demonstrate this, by attempting to create an asyncio task from a Python function.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
# SuperFastPython.com # example of using a traditional function with an asyncio task 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') # create and schedule the task task = asyncio.create_task(task_coroutine()) # wait for the task to complete await task # start the asyncio program asyncio.run(main()) |
Running the example fails with an exception.
In this case, a TypeError is raised with a message that a coroutine was expected, but was not provided.
This highlights what to expect when this common error is made.
1 2 3 4 5 |
main coroutine executing the task Traceback (most recent call last): ... TypeError: a coroutine was expected, got None |
The fix involves changing the function into a coroutine by changing “def” into “async def“.
For example:
1 2 3 4 |
# define a coroutine for a task async def task_coroutine(): # report a message print('executing the task') |
Next, let’s look at what happens if we attempt to create a task from a coroutine incorrectly.
Example of a Task Wrapping a Coroutine Incorrectly
We may accidentally attempt to create a task from the definition of a coroutine.
This can happen if we provide the name of a coroutine instead of an instance of a coroutine.
For example:
1 2 3 |
... # create and schedule the task task = asyncio.create_task(task_coroutine) |
This is a problem because of the asyncio.create_task() method expects an instance of a coroutine.
This is a common problem and can be made by an accidental typing error or by beginners.
The example below demonstrates this common error with a worked example.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
# SuperFastPython.com # example of creating an asyncio task from a coroutine incorrectly import asyncio # define a coroutine for a task async def task_coroutine(): # report a message print('executing the task') # custom coroutine async def main(): # report a message print('main coroutine') # create and schedule the task task = asyncio.create_task(task_coroutine) # wait for the task to complete await task # start the asyncio program asyncio.run(main()) |
Running the example fails with an exception.
In this case, a TypeErorr is raised with a message that a coroutine object was expected, not a definition of a coroutine.
This highlights what to expect when this common error is made.
1 2 3 4 |
main coroutine Traceback (most recent call last): ... TypeError: a coroutine was expected, got <function task_coroutine at 0x100bc2e60> |
The fix involves changing the call to asyncio.create_task() to take an instance of the coroutine and not the name of the defined coroutine.
For example:
1 2 3 |
... # create and schedule the task task = asyncio.create_task(task_coroutine()) |
Further Reading
This section provides additional resources that you may find helpful.
Python Asyncio Books
- Python Asyncio Mastery, Jason Brownlee (my book!)
- Python Asyncio Jump-Start, Jason Brownlee.
- Python Asyncio Interview Questions, Jason Brownlee.
- Asyncio Module API Cheat Sheet
I also recommend the following books:
- Python Concurrency with asyncio, Matthew Fowler, 2022.
- Using Asyncio in Python, Caleb Hattingh, 2020.
- asyncio Recipes, Mohamed Mustapha Tahrioui, 2019.
Guides
APIs
- asyncio — Asynchronous I/O
- Asyncio Coroutines and Tasks
- Asyncio Streams
- Asyncio Subprocesses
- Asyncio Queues
- Asyncio Synchronization Primitives
References
Takeaways
You now know how to create and use asyncio tasks in Python.
Do you have any questions?
Ask your questions in the comments below and I will do my best to answer.
Photo by Olav Tvedt on Unsplash
Do you have any questions?