You must have used the functions in the Python program to perform a certain task. These functions in the Python program are known as subroutines. Subroutines follow a set pattern of execution like entering at one point(subroutine or function call) and exiting at another point(return statement).
Coroutines are similar to subroutines but unlike subroutines, coroutines can enter, exit, and resume at different points during the execution. Coroutines are used for cooperative multitasking which means the control is passed from one task to another to enable multiple tasks to run simultaneously.
Introduction To Coroutines
Coroutines (generator-based coroutines) are a specialized version of generators and like them, they can be paused and resumed using the yield keyword at the time of execution.
Generators generate data, whereas coroutines can do both, generating and consuming data, with a slight difference in how the yield is used within coroutines. We can use yield as an expression (value = yield
) within coroutines, which means that yield
can both generate and consume values.
To justify the above point, consider the following example in which we created a function that exhibits the behavior of a coroutine.
1 2 3 4 5 6 7 8 9 10 |
def cor_func(char): print(f"Searching for character: {char}") while True: data = yield if char in data: print("True") else: print("False") value = cor_func("e") |
The above code defines a coroutine function called cor_func
, which searches for a parameter char
. The function cor_func
uses while True
to run an infinite loop, and within the loop, yield
is encountered, which is a halt in the execution and allows us to send data in the meantime. The caller’s data is saved inside the variable data
.
If the character char
is present in the data
, the function prints the message True
, otherwise, the message False
is printed.
We created the instance of the coroutine (cor_func("e")
) and stored it inside the variable value
.
1 2 3 4 |
value.__next__() value.send("hello") value.send("GeekPython") value.send("Geek") |
The coroutine is started by calling value.__next__()
. The coroutine function will execute until it reaches the yield
, allowing us to send data.
We first sent the string "hello"
using the value.send("hello")
and the string will be checked if the character "e"
is present in it, since there is "e"
in "hello"
, the output would be True
. The same process will be repeated for value.send("GeekPython")
and value.send("Geek")
as well.
1 2 3 4 |
Searching for character: e True True True |
Closing the Coroutine
The close()
method, as the name implies, is used to close the coroutine, which means that no more values can be sent to the coroutine.
Consider the previous code, which we modified by adding the value.close()
method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
def cor_func(char): print(f"Searching for character: {char}") while True: data = yield if char in data: print("True") else: print("False") value = cor_func("e") value.__next__() value.send("hello") value.send("GeekPython") value.close() value.send("Geek") |
We called the close()
method on the coroutine, which will close the coroutine and prevent it from receiving further values. However, when we tried to send the string "Geek"
to the coroutine, we got the following result.
1 2 3 4 5 6 7 |
Searching for character: e True True Traceback (most recent call last): .... value.send("Geek") StopIteration |
Since the coroutine had already been closed, the send()
method that followed the close()
method threw the StopIteration
exception.
Async Coroutine
We can define a coroutine function (async def
) and pause the process until a specific task is completed by using the (async
/await
) keywords.
1 2 3 4 5 6 7 8 9 10 11 12 |
import asyncio async def coroutine_func(): print("Coroutine started.") await asyncio.sleep(1) print("Coroutine ended.") cor = coroutine_func() print(cor) ---------- <coroutine object coroutine_func at 0x0000023A863280C0> |
The asyncio
module was imported, which allows us to write asynchronous code. Then we defined the asynchronous coroutine function (coroutine_func()
), which prints a message and then waits one second before printing another message using await asyncio.sleep(1)
.
We created a coroutine function instance and stored it in the variable cor
. When we printed, the coroutine object was returned.
We can use the asyncio.run()
function and pass in our coroutine object cor
to run the above coroutine.
1 |
asyncio.run(cor) |
Concurrency Using Coroutine
We can think of it as the ability to run multiple tasks concurrently in an overlapping manner. Let’s understand with an example.
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 |
import asyncio # Task 1 async def read_file(): with open("test.txt") as data: await asyncio.sleep(1) print(data.read()) # Task 2 async def write_file(): with open("test.txt", "a") as data: await asyncio.sleep(1) data.write("\nIt will be fun.") # Task 3 async def message(): print("Hey,") await asyncio.sleep(1) print("Welcome aboard.") # Entry point async def main(): await asyncio.gather(message(), write_file(), read_file()) if __name__ == "__main__": import time start = time.perf_counter() asyncio.run(main()) elapsed = time.perf_counter() - start print(f"Tasks executed in {elapsed:0.1f} seconds.") |
Three coroutine functions are defined in the preceding code.
The coroutine function read_file()
opens the file test.txt
, waits for one second, reads the content, and then prints it.
The coroutine function write_file()
opens the file in append mode, waits for one second, writes the data, and then appends to the file.
The message()
coroutine function prints one message, then waits for one second before printing another.
The coroutine function main()
is defined to run those three coroutine functions concurrently using asyncio.gather(message(), write_file(), read_file())
.
Inside the if __name__ == "__main__":
block, we executed our main()
coroutine function using asyncio.run(main())
and we used the time.perf_counter()
to measure the execution time.
1 2 3 4 5 |
Hey, Welcome aboard. We are currently learning coroutines. It will be fun. Tasks executed in 1.0 seconds. |
The code took 1.0
seconds to execute all three coroutine functions which were due to 1 second delay in the coroutines. These coroutine functions were executed simultaneously.
Awaiting Coroutine
Coroutines are awaitables (objects that can be used in an await
expression), so they can be awaited from other coroutines. Let’s look at the example to get a grasp of it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import asyncio async def read_file(): with open("test.txt") as file: return file.read() async def write_file(): with open("test.txt", "a+") as file: file.write("\nLet's get started.") # Awaiting read_file() from write_file() coroutine print(await read_file()) asyncio.run(write_file()) |
When we run the above code with asyncio.run(write_file())
, the coroutine function write_file()
is called first, and it opens the test.txt
file in append mode and appends the data. Then it proceeds until it encounters the await read_file()
, which halts the execution of the write_file()
coroutine function.
The execution flow then proceeds to the read_file()
coroutine function, which reads the contents of the test.txt
file.
When the execution flow returns, the content returned by read_file()
is printed.
1 2 3 |
We are currently learning coroutines. It will be fun. Let's get started. |
Inside the test.txt
file, we can see that the string "Let's get started"
has been appended to our existing data.
Conclusion
Coroutines are very helpful in asynchronous programming in which multiple tasks run concurrently. We’ve seen how multiple coroutines are executed concurrently to save time.
Coroutines can enter, exit, and resume at different points during the execution. They are similar to generators but they have additional features such as support for cooperative multitasking, asynchronous programming, and more.
🏆Other articles you might be interested in if you liked this one
✅How to use assert statements for debugging in Python?
✅How to manipulate paths using pathlib module in Python?
✅Different types of inheritances in Python classes.
✅How to implement __getitem__, __setitem__ and __delitem__ in Python classes?
✅How to connect PostgreSQL with Python using psycopg2?
✅How to display images on the frontend using FastAPI?
✅Public, Protected, and Private access modifiers in Python.
That’s all for now
Keep coding✌✌