Coroutines In Python

Overview:

  • Functions, Generators, Coroutines - are all different types of blocks of codes falling under the same category - code routines.

  • In Python, functions correspond to subroutines and they can not be suspended and resumed.

  • The generators and coroutines are types of functions that can be suspended and resumed.

  • Both generators and coroutines are suspended with a yield statement.

  • The yield statement is crucial to asynchronous programming in python. Both generators and coroutines use the yield statement to suspend their execution in which it is invoked.Generators use them directly. Coroutines use them, when they await on an awaitable.

  • The idea is to suspend the execution, while encountering a blocking call like IO(as in case of a coroutine) or suspend the execution simply to return a value(as in case of generator) - in both the cases yield is there if a suspension of the routine is needed. If there is no yield somewhere down, when await is called, then it will be considered as non-asynchronous code execution and there is nothing to wait or no reason or note provided by the code to yield.

  • If the code is not suspended while a time consuming operation is executed then the event loop that executes the coroutine can not work properly as it violates the principle of co-operative multitasking.

  • Unlike threads which are OS level entities, a coroutine is a language level entity.

  • Generators are category of Coroutines designed to generate a set of values over a period of time, maintaining the state (between a suspend and resume)while producing such set of values. Python provides mechanisms to communicate with generator functions and coroutines from the callers - which can be used, based on the needs of the code(through the send() and throw() methods).

  • Any piece of code, that has access to a function object (a call back function) can call it(only call it - can not suspend it or resume it). In the similar way, any piece of code that has access to a coroutine object or a generator object can start them and resume them after a suspension.

  • Calling a generator function or a coroutine returns a generator object or a coroutine object.

Writing a coroutine in Python:

  • A coroutine is destined for suspension 1 to n times before its life is complete( 0 times is the special case covered at the last portion of this section) Hence, it is required to decide on which awaitable a coroutine is to going to await on. If the awaitable has been already written and available to the developer then the developer can just write an await statement specifying an awaitable.
  • If the awaitable needs to be written fresh, then the developer writes it and await for it from the coroutine.
  • A coroutine has the form of

async def corutinename():

await awaitable

  • An awaitable could be another coroutine or any object that implements the __await__() function.
  • A coroutine can not have an yield statement and an awaitable somewhere down the line has to have an yield statement. Remember, in Python only an issuance of yield can explicitly suspend the execution of an awaiting/surrounding coroutine.
  • If yield is not provided the code will not be suspended it is just synchronous execution.

Example of a coroutine in Python:

  • The example reads from two text files and prints a line alternatively from each of them.
  • The coroutine awaits on a FileReader object, which yields on every line that is read from a disk file.
  • The example does not uses the asyncio module. It provides the start and subsequent resumes to the coroutine objects through their send() method within a while loop.

# Create an awaitable 
class FileReader:
    def __init__(self, file):
        self.file = open(file)

    def __iter__(self): 
        return self        

    def __await__(self):
        while(True):
            ln = self.file.readline()
            if not ln:
                break
            else: 
                yield ln 

# Define a coroutine
async def getLines(fr):
    await fr

# Create two FileReader objects
path1 = "./tokens1.txt"
path2 = "./tokens2.txt"

fr1 = FileReader(path1)
fr2 = FileReader(path2)

# Create two coroutine objects
coro1 =     getLines(fr1)
coro2 =     getLines(fr2)

# Run the coroutines in a simple loop
try:
    while(True):
        print(coro1.send(None))
        print(coro2.send(None))
except StopIteration as ex:
    print(type(ex))

Output:

a1

 

a2

 

b1

 

b2

 

c1

 

c2

 

d1

 

d2

 

 

 

 

 

<class 'StopIteration'>

 


Copyright 2023 © pythontic.com