People often get confused by asynchronous programming because they think it's too complicated. At the heart, it's a very simple concept. In this blog, I will explain important concepts in asynchronous programming with examples using Python and ayncio
library. Let's get started.
Synchronous vs. Asynchronous
Synchronous programming means one task runs after the other. If a task takes time, the whole program waits for it to complete.
import time
def task1():
print("Starting task 1")
time.sleep(3) # Simulates a task that takes 3 seconds
print("Task 1 complete")
def task2():
print("Starting task 2")
time.sleep(2) # Simulates a task that takes 2 seconds
print("Task 2 complete")
task1()
task2()
# Expected Output
"""
Starting task 1
Task 1 complete
Starting task 2
Task 2 complete
"""
Asynchronous programming allows tasks to run concurrently. Instead of waiting for each task to finish, multiple tasks can progress at the same time to save time.
import asyncio
async def task1():
print("Starting task 1")
await asyncio.sleep(3) # Simulates a task that takes 3 seconds
print("Task 1 complete")
async def task2():
print("Starting task 2")
await asyncio.sleep(2) # Simulates a task that takes 2 seconds
print("Task 2 complete")
async def main():
await asyncio.gather(task1(), task2()) # Run both tasks concurrently
asyncio.run(main())
# Expected output
"""
Starting task 1
Starting task 2
Task 2 complete
Task 1 complete
"""
In this code example, I used asyncio
library and its functionalities. async
and await
are key syntaxes in asynchronous programming that allow users to write code easily. async
is used to declare a function as asynchronous, and await
pauses the execution of the function until the awaited task is complete. I will explain throughout this blog what each function does.
Coroutines
Coroutines are special functions that can be paused and resumed later. They are the foundation of asynchronous programming in Python. Think of coroutines as a function that you want to execute asynchronously.
import asyncio
async def my_coroutine():
print("Coroutine started")
await asyncio.sleep(2) # Simulates waiting for 2 seconds
print("Coroutine ended")
asyncio.run(my_coroutine())
# Expected output
"""
Coroutine started
Coroutine ended
"""
Here in the example, my_coroutine()
pauses for 2 seconds when it reaches await asyncio.sleep(2)
, and resumes to finish the execution. Coroutines are just another name for asynchronous functions.
Event Loop
Event Loop is the most important concept. This is what makes asynchronous programming possible. It manages multiple tasks, pausing them when they need to wait, and resuming them when they are ready. Its job is to manage and execute coroutines by checking if they're ready to run, pausing them when they need to wait, and resuming them when the waiting is over. The event loop continuously runs until all tasks (coroutines) are complete.
import asyncio
async def task():
print("Task started")
await asyncio.sleep(1) # Simulate a task that takes 1 second
print("Task completed")
# Start the event loop with asyncio.run()
asyncio.run(task())
# Expected Output
"""
Task started
Task completed
"""
In this code example, here's what happens:
- Starting the Event Loop:
- When you call
asyncio.run(task())
, Python starts the event loop. The event loop begins managing the coroutinetask()
. - Executing the Coroutine:
- The event loop starts running
task()
, and the first line prints"Task started"
. - Pausing with await:
- When the coroutine hits await
asyncio.sleep(1)
, it signals to the event loop: "I'm waiting for 1 second, so pause me." The event loop pauses the coroutine and checks if there are other tasks it can run (in this simple example, there are none). - Resuming the Coroutine:
- After 1 second has passed, the event loop resumes the
task()
coroutine and runs the remaining code, printing"Task completed"
. - Ending the Event Loop:
- Once all tasks (in this case, just
task()
) are complete, the event loop stops.
The event loop is essential in asynchronous programming because it allows your program to:
- Run multiple tasks concurrently: The event loop can switch between tasks that are waiting (e.g., for network responses) and tasks that are ready to run.
- Avoid blocking: When tasks need to wait (like for I/O), the event loop can handle other tasks, making the program more efficient.
Gathering Multiple Tasks
In asynchronous programming, gathering tasks refers to running multiple asynchronous tasks concurrently. Don't get confused with Event Loop. Event Loop is a manager that oversees all async processes. Gathering tasks is a way to tell the event loop to run multiple specific tasks concurrently.
In asyncio
library, gather()
is used to collect multiple coroutines and run them simultaneously without waiting for one to finish before starting another. For example, if you need to download data from several websites or perform multiple I/O operations, you can use gather()
to start all these tasks at once, reducing the total time needed to complete them.
import asyncio
async def task1():
print("Task 1 started")
await asyncio.sleep(2)
print("Task 1 complete")
async def task2():
print("Task 2 started")
await asyncio.sleep(1)
print("Task 2 complete")
async def task3():
print("Task 3 started")
await asyncio.sleep(3)
print("Task 3 complete")
async def main():
# Gather task1 and task2 to run concurrently
await asyncio.gather(task1(), task2())
# task3 runs after task1 and task2 are completed
await task3()
# Start the event loop to run the tasks
asyncio.run(main())
# Expected output
"""
Task 1 started
Task 2 started
Task 2 complete
Task 1 complete
Task 3 started
Task 3 complete
"""
In this example, here’s what happens:
- When
asyncio.run(main())
is called, Event Loop starts and manages the execution of all the coroutines. asyncio.gather()
runstask1()
andtask2()
concurrently. Event Loop is switching between them based on when they are waiting (i.e.,await asyncio.sleep()
)- Once the gathered tasks (
task1()
andtask2()
) are done, the event loop moves on totask3()
, which runs on its own after the others have completed.
Wrapping Up
Asynchronous programming allows you to write more efficient Python programs by running multiple tasks concurrently, especially when waiting is involved (like network requests or file operations). I used asyncio
library to explain in Python, but async programming exists in any language. I hope this was helpful. By now, you should feel confident about explaining what asynchronous programming :)