You can run an asyncio task periodically in the background.
This requires developing a new periodic() coroutine that runs in a loop forever, each iteration sleeping for a given number of seconds and awaiting a target coroutine.
In this tutorial, you will discover how to develop a periodic task in asyncio programs.
Let’s get started.
Need to Run a Periodic Task
Generally, a periodic task is a task that runs in the background repeatedly in a loop.
These tasks are designed to repeat automatically after a certain predefined fixed interval of time, often to perform routine or recurring actions within a software application. They provide a way to automate essential operations, ensuring that they are carried out consistently without manual intervention.
The repeated nature of the periodic task makes them different from other types of background tasks, like delayed tasks.
You can learn more about delayed asyncio tasks in the tutorial:
Periodic tasks are valuable in various domains, from system maintenance and data processing to application monitoring and notification scheduling. They play a crucial role in maintaining the reliability and functionality of many software systems.
We often need to perform a periodic task in an asyncio program.
This may be for many reasons, such as:
- Scheduled Maintenance: Perform routine maintenance tasks, such as database cleanup, cache clearing, or log rotation, on a regular schedule to keep the application running smoothly.
- Data Synchronization: Periodically synchronize data between different systems, databases, or services to ensure data consistency and accuracy.
- Background Processing: Run background tasks at predefined intervals to process data, generate reports, or perform calculations without blocking the main event loop.
- Monitoring and Alerting: Periodically check the health and performance of the application and its dependencies. Send alerts or notifications when predefined conditions or thresholds are met.
- Resource Management: Manage and monitor system resources, such as memory usage, CPU load, or network bandwidth, to prevent resource exhaustion and optimize resource allocation.
- Caching and Invalidation: Refresh or invalidate cached data or results at regular intervals to ensure that users receive up-to-date information.
- Scheduled Jobs: Schedule jobs for specific times or intervals, such as sending email newsletters, generating backups, or processing batch uploads.
- Rate Limiting: Implement rate limiting or throttling mechanisms to control the frequency at which certain actions or requests can be performed, reducing the risk of abuse.
- Data Aggregation: Aggregate data from multiple sources over time, such as collecting statistics, logs, or metrics for reporting and analysis.
- Security Scanning: Perform periodic security scans and vulnerability assessments to identify and address potential security issues.
Given that periodic tasks are an important part of application development, how can we develop coroutines and tasks to run periodically in the background?
How can we develop a periodic task in asyncio?
Run loops using all CPUs, download your FREE book to learn how.
How to Run a Periodic Task
We can develop a periodic task in asyncio as a coroutine that runs in a loop.
The coroutine takes the name of the target coroutine to run and any arguments to the coroutine, as well as the interval of time in seconds between running the target coroutine.
It then loops forever first sleeping for the provided fixed interval and then running and awaiting the target coroutine.
1 2 3 4 5 6 7 8 |
# helper function for running a target periodically async def periodic(interval_sec, coro_name, *args, **kwargs): # loop forever while True: # wait an interval await asyncio.sleep(interval_sec) # await the target await coro_name(*args, **kwargs) |
The periodic() coroutine can then be run as a background task in an asyncio program.
This can be achieved by creating and scheduling the configured periodic() coroutine as an asyncio task via the asyncio.create_task() function.
For example:
1 2 3 |
... # create and schedule the periodic task task = asyncio.create_task(periodic(1.0, work)) |
The asyncio program can then proceed with other tasks while the periodic coroutine runs in the background.
You can learn more about running asyncio coroutines in the background in the tutorial:
Why Not Provide a Task to periodic()?
We cannot provide an asyncio.Task instance to the periodic() coroutine because asyncio.Task instances cannot be run more than once.
Attempting to run the same asyncio.Task task more than once results in an exception.
This is why we provide the name of a target coroutine and a new coroutine is created each time the loop runs within the periodic() coroutine.
Why Not Provide a Coroutine Object to periodic()?
We cannot provide a coroutine object to the periodic() coroutine.
The reason is that we cannot run the same coroutine more than once as it results in a RuntimeError exception:
1 |
RuntimeError: cannot reuse already awaited coroutine |
This is why we provide the name of a target coroutine and a new coroutine is created each time the loop runs within the periodic() coroutine.
Don’t We Need to Explicitly Stop the periodic() Task?
We do not need to explicitly stop or cancel the periodic() background task.
This is because the periodic() will be canceled automatically by the asyncio event loop when it is shut down.
We can choose to cancel the periodic task ourselves before closing our program. This can be helpful if the target coroutine needs to perform some cleanup before some other task, e.g. when performing cleanup when shutting down the event loop is not a good fit.
This can be achieved by calling the cancel() method on the periodic() task before closing the program.
For example:
1 2 3 4 5 6 7 8 |
... # cancel the background task task.cancel() # wait for the task to cancel try: await task except asyncio.CancelledException: pass |
This is called a cancel-and-wait pattern.
You can learn more about this pattern in the tutorial:
Now that we know how to run a periodic background task in asyncio, let’s look at a worked example.
Example of Periodic Task
We can explore an example of running a periodic task in asyncio.
In this example, we will define a task that simply reports a message.
1 2 3 4 |
# general task async def work(): # report a message print('>Doing work:!') |
We will then run this task every second using our periodic() coroutine developed above as a background task and allow it to run for a while.
1 2 3 4 5 6 7 8 9 10 |
# main coroutine async def main(): # report a message print('Main starting') # configure the periodic task task = asyncio.create_task(periodic(1.0, work)) # do other things for a while await asyncio.sleep(5) # report a message print('Main done') |
Tying this together, 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 30 31 |
# SuperFastPython.com # example of periodic task import asyncio # helper function for running a target periodically async def periodic(interval_sec, coro_name, *args, **kwargs): # loop forever while True: # wait an interval await asyncio.sleep(interval_sec) # await the target await coro_name(*args, **kwargs) # general task async def work(): # report a message print('>Doing work:!') # main coroutine async def main(): # report a message print('Main starting') # configure the periodic task task = asyncio.create_task(periodic(1.0, work)) # do other things for a while await asyncio.sleep(5) # report a message print('Main done') # start the event loop asyncio.run(main()) |
Running the example first starts the asyncio event loop and runs the main() coroutine.
The main() coroutine runs and reports a message.
It then creates and schedules the periodic() coroutine as a background task. The arguments to the periodic() coroutine are one second and the name of the “work” coroutine.
The main() coroutine then suspends and sleeps for 5 seconds.
The periodic() task runs and starts the while loop that runs forever.
In each iteration of the loop the periodic() task sleeps for one second, then creates and awaits the work() coroutine with no arguments.
The work() coroutine runs and reports a message, then terminates.
This cycle of sleeping and awaiting the work() coroutine is repeated a number of times.
Eventually, the main() coroutine resumes and reports a final message before terminating.
This closes the asyncio event loop and causes the periodic background task to terminate.
This highlights how we can execute a coroutine periodically in the background in our asyncio program.
1 2 3 4 5 6 |
Main starting >Doing work:! >Doing work:! >Doing work:! >Doing work:! Main done |
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.
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
Overwhelmed by the python concurrency APIs?
Find relief, download my FREE Python Concurrency Mind Maps
Takeaways
You now know how to develop a periodic task in asyncio programs.
Did I make a mistake? See a typo?
I’m a simple humble human. Correct me, please!
Do you have any additional tips?
I’d love to hear about them!
Do you have any questions?
Ask your questions in the comments below and I will do my best to answer.
Photo by carolyn christine on Unsplash
Do you have any questions?