Python asyncio: Display a loading indicator in the terminal while waiting for a task to complete

Updated: February 12, 2024 By: Guest Contributor Post a comment

Overview

In modern Python development, especially with the advent of Python 3.7 and 3.11, asynchronous programming has become more important than ever. With asyncio, Python provides a robust set of tools for writing concurrent code using coroutines. In this tutorial, we’ll explore how to use asyncio in Python 3.11 (or higher) to display a loading indicator in the terminal while waiting for a task to complete. This can enhance the user experience in CLI applications by providing visual feedback during long-running operations.

Understanding asyncio

Before we dive into the implementation of the loading indicator, let’s briefly touch on what asyncio is. asyncio is a library in Python that provides infrastructure for writing single-threaded concurrent code using coroutines, multiplexing I/O access over sockets and other resources, running network servers, and other related primitives. In simple terms, it lets you write code that can handle a lot of I/O-bound and high-level structured network code asynchronously.

Setting up Your Environment

Ensure you’re running Python 3.11 or newer. You can check your Python version by running python --version in your terminal. If you’re not on Python 3.11, consider using a tool like pyenv to manage multiple Python versions.

Writing an Asynchronous Loading Indicator

Let’s dive into writing the asynchronous loading indicator. The idea is to display a simple animation (like a rotating `|/-\` symbol) in the terminal while an asynchronous task is running and to stop it once the task is complete.

import asyncio
import itertools
import sys

def print_with_carriage_return(status):
    sys.stdout.write('\r' + status)
    sys.stdout.flush()

class LoadingIndicator:
    def __init__(self, msg='Loading...', symbols='|/-\\'):
        self.msg = msg
        self.symbols = itertools.cycle(symbols)

    async def start(self):
        while True:
            sys.stdout.write('\r' + self.msg + ' ' + next(self.symbols))
            sys.stdout.flush()
            await asyncio.sleep(0.1)

    async def stop(self):
        sys.stdout.write('\r')
        sys.stdout.flush()

We have created a class LoadingIndicator that when its start method is called, will print the loading message along with the symbols in rotation. To stop the indicator, the stop method clears the line.

Integrating the Loading Indicator with Asyncio

To use the loading indicator, you will need to run it concurrently with your main async task, allowing it to update the terminal output while the main task is still in progress. Here’s how you can integrate both:

import asyncio

async def main_task(duration=10):
    # simulate a long-running task
    await asyncio.sleep(duration)
    print("\nTask completed!")

async def main():
    loader = LoadingIndicator()
    loader_task = asyncio.create_task(loader.start())  # Start loading indicator
    await main_task()  # This simulates your long-running task
    await loader.stop()  # Stop the loading indicator after the task is completed

if __name__ == '__main__':
    asyncio.run(main())

In this example, the main_task simulates a long-running operation with asyncio.sleep. We then create a task for the LoadingIndicator‘s start method using asyncio.create_task(), which starts the loading animation. After main_task completes, loader.stop() is called to stop the animation.

Handling Multiple Asynchronous Tasks

In more complex applications, you might find yourself managing multiple asynchronous tasks alongside the loading indicator. The following example shows how to use asyncio.gather to run multiple tasks concurrently:

import asyncio

async def additional_task(time):
    await asyncio.sleep(time)

async def main():
    loader = LoadingIndicator()
    tasks = [loader.start(), main_task(), additional_task(5)]
    await asyncio.gather(*tasks)
    await loader.stop()  # Be sure to stop the loader

if __name__ == '__main__':
    asyncio.run(main())

Note that in this setup, you will need to manually ensure that loader.stop() is called after all tasks have been completed to properly stop the loading indicator.

Conclusion

You’ve learned how to created a loading indicator from scratch in Python. You can modify the code or extend it to make the result even better. Try to remove some lines of code, add your own code, and see what happens next. The tutorial ends here. Happy coding & have a nice day!