DEV Community

Uzziel Kyle
Uzziel Kyle

Posted on

Getting Started with Asynchronous Programming in Python

You may have heard of the term "asynchronous" in programming. Sounds fancy, eh? But what is it all about? In this post, I'll introduce you to the basic concept of asynchronous programming in Python.

Here, you'll learn about:

  • The difference between synchronous and asynchronous programming
  • Routines and coroutines
  • Use cases of asynchronous programming
  • How to create a basic asynchronous program in Python

Synchronous vs Asynchronous Programming

There are two approaches to handling tasks in a program. In the synchronous approach, tasks are performed one at a time in the order that they are called in. Tasks are block-driven, meaning that each task must finish before the next one can begin. The synchronous approach leverages the use of routines, also known as functions. This is the common approach most of us, especially beginners, take.
a diagram showing how subroutines work

The other approach is the asynchronous, where different tasks can start, process, and finish in overlapping periods of time. It works by using coroutines. A coroutine is a special kind of function that can be paused and resumed. It's able to do so because it can maintain its state between pauses. By using coroutines, programs can run tasks concurrently.
a diagram showing how coroutines work

The asynchronous approach is commonly used for I/O operations, database queries, and HTTP requests. You basically apply async for tasks that needs to wait.

To better understand these two approaches, we will create a Python program to demonstrate synchronous and asynchronous programming.

A Basic Program in Python

We will start by creating a basic program that let us do the tasks named eat_food() and watch_video(). We'll execute both of these tasks when we run our program.

First we will import the built-in time module to (1) simulate the amount of time each task consumed and (2) record the execution time it took to run both tasks.

import time  # for getting execution time
Enter fullscreen mode Exit fullscreen mode

Then, we define our functions.

def eat_food():
    print('Start eat_food()')
    time.sleep(5) # I eat for 5 mins.
    print('End eat_food()')

    return 'Finished eating'

def watch_video():
    print('Start watch_video()')
    time.sleep(8) # I watch an 8 min. video
    print('End watch_video()')

    return 'Finished watching'
Enter fullscreen mode Exit fullscreen mode

Create a main() function and execute both tasks inside.

def main():
    start_time = time.time()

    result_eat = eat_food()
    result_watch = watch_video()

    end_time = time.time()
    execution_time = end_time - start_time

    print(f'Result of eat_food(): {result_eat}')
    print(f'Result of watch_video(): {result_watch}')
    print(f'Total execution time: {execution_time:.2f} seconds')


if __name__ == '__main__':
    main()

Enter fullscreen mode Exit fullscreen mode

Run the program, and the output should look like this.

Start eat_food()
End eat_food()
Start watch_video()
End watch_video()
Result of eat_food(): Finished eating
Result of watch_video(): Finished watching
Total execution time: 13.00 seconds
Enter fullscreen mode Exit fullscreen mode

Notice that it took 13 seconds to execute both tasks: five seconds for eat_food() and eight seconds for watch_video(). The task were ran sequentially. This is how synchronous programming works.

Source Code

import time  # for getting execution time


def eat_food():
    print('Start eat_food()')
    time.sleep(5) # I eat for 5 mins.
    print('End eat_food()')

    return 'Finished eating'


def watch_video():
    print('Start watch_video()')
    time.sleep(8) # I watch an 8 min. video
    print('End watch_video()')

    return 'Finished watching'


def main():
    start_time = time.time()

    result_eat = eat_food()
    result_watch = watch_video()

    end_time = time.time()
    execution_time = end_time - start_time

    print(f'Result of eat_food(): {result_eat}')
    print(f'Result of watch_video(): {result_watch}')
    print(f'Total execution time: {execution_time:.2f} seconds')


if __name__ == '__main__':
    main()

Enter fullscreen mode Exit fullscreen mode

Asynchronous Program in Python

To demonstrate asynchronous programming in Python, we will apply some modification to the program we just made. To do this, we will use a built-in library called Asyncio.

import asyncio
import time  # for getting execution time
Enter fullscreen mode Exit fullscreen mode

To turn a function into a coroutine, add the async keyword to the function.

async def eat_food():
    print('Start eat_food()')
    ...

async def watch_video():
    print('Start watch_video()')
    ...
Enter fullscreen mode Exit fullscreen mode

In a coroutine, you can use the await keyword to specify what part of the code it is safe to pause.

async def eat_food():
    print('Start eat_food()')
    await asyncio.sleep(5)  # I eat for 5 mins.
    print('End eat_food()')

    return 'Finished eating'


async def watch_video():
    print('Start watch_video()')
    await asyncio.sleep(8) # I watch an 8 min. video
    print('End watch_video()')

    return 'Finished watching'
Enter fullscreen mode Exit fullscreen mode

Note: You can only put await in an "awaitable" command, typically these are also coroutines.

If you think that you can call coroutines the same way as you call normal functions, I also thought so. However, it is not how it works. Coroutines return coroutine objects when you call them as is.

Result of eat_food(): <coroutine object eat_food at 0x000001A73ED2D180>
Result of watch_video(): <coroutine object watch_video at 0x000001A73ED2D9C0>
Enter fullscreen mode Exit fullscreen mode

Calling coroutines properly can be done individually or by batch.

Individually using asyncio.create_task()

def main():
    task_eat = async.create_task(eat_food())
    task_watch = async.create_task(watch_video())
Enter fullscreen mode Exit fullscreen mode

By batch using asyncio.gather()

def main():
    batch = asyncio.gather(eat_food(), watch_video())
Enter fullscreen mode Exit fullscreen mode

To get the return values of coroutines, you need to await them

Individually

def main():
    task_eat = async.create_task(eat_food())
    task_watch = async.create_task(watch_video())

    result_eat = await task_eat  # 'Finished eating'
    result_watch = await task_watch  # 'Finished watching'
Enter fullscreen mode Exit fullscreen mode

By batch

def main():
    batch = asyncio.gather(eat_food(), watch_video())

    result_eat, result_watch = await batch  # ('Finished eating', 'Finished watching')
Enter fullscreen mode Exit fullscreen mode

Since we have used await inside the main() function,

async def main():
    ...
Enter fullscreen mode Exit fullscreen mode

Note: any function that contains the await keyword must be declared an async function

Here's the final look of our main() function.

Individually

async def main():
    start_time = time.time()

    task_eat = async.create_task(eat_food())
    task_watch = async.create_task(watch_video())

    result_eat = await task_eat  # 'Finished eating'
    result_watch = await task_watch  # 'Finished watching'

    end_time = time.time()
    execution_time = end_time - start_time

    print(f'Result of eat_food(): {result_eat}')
    print(f'Result of watch_video(): {result_watch}')
    print(f'Total execution time: {execution_time:.2f} seconds')
Enter fullscreen mode Exit fullscreen mode

By batch

async def main():
    start_time = time.time()

    batch = asyncio.gather(eat_food(), watch_video())

    result_eat, result_watch = await batch  # ('Finished eating', 'Finished watching')

    end_time = time.time()
    execution_time = end_time - start_time

    print(f'Result of eat_food(): {result_eat}')
    print(f'Result of watch_video(): {result_watch}')
    print(f'Total execution time: {execution_time:.2f} seconds')
Enter fullscreen mode Exit fullscreen mode

To run our program, we will use asyncio.run() and pass main() as an argument.

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

Enter fullscreen mode Exit fullscreen mode
Start eat_food()
Start watch_video()
End eat_food()
End watch_video()
Result of eat_food(): Finished eating
Result of watch_video(): Finished watching
Total execution time: 8.00 seconds 
Enter fullscreen mode Exit fullscreen mode

In the asynchronous approach, we managed to save five seconds in our execution time by allowing our tasks to run concurrently. This is how coroutines work.

Source Code

import asyncio
import time  # for getting execution time


async def eat_food():
    print('Start eat_food()')
    await asyncio.sleep(5)  # I eat for 5 mins.
    print('End eat_food()')

    return 'Finished eating'


async def watch_video():
    print('Start watch_video()')
    await asyncio.sleep(8) # I watch an 8 min. video
    print('End watch_video()')

    return 'Finished watching'


async def main():
    start_time = time.time()

    task_eat = asyncio.create_task(eat_food())
    task_watch = asyncio.create_task(watch_video())

    result_eat = await task_eat  # 'Finished eating'
    result_watch = await task_watch  # 'Finished watching'

    end_time = time.time()
    execution_time = end_time - start_time

    print(f'Result of eat_food(): {result_eat}')
    print(f'Result of watch_video(): {result_watch}')
    print(f'Total execution time: {execution_time:.2f} seconds')


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

Enter fullscreen mode Exit fullscreen mode

Conclusion

By now, you have at least the basic idea of the difference between synchronous and asynchronous programming, what kind of function is used in the asynchronous approach, and how to create a basic async program in Python.

In a later post, I'll show you how to apply these concepts on real life by building a basic GUI app.

Key Terms

  • Synchronous - tasks are performed one at a time
  • Asynchronous - multitasking; also known as async
  • Routine - a block of code that can be called as needed; also known as function
  • Coroutine - a special kind of function that can be paused and resumed
  • asyncio - built-in Python library for doing asynchronous programming

Thanks for reading! I hope you enjoyed this article. If you have some suggestions or criticism, feel free to email me🙂

See you on my next post 😉

Top comments (0)