Open In App

Python Taskgroups with asyncIO

Improve
Improve
Like Article
Like
Save
Share
Report

In this article, we will see the use of Task Groups with asyncIO in Python.

What is a Task group?

Task groups are introduced in Python 3.11 along with Exception Groups and as the name suggests it groups similar or different tasks together. It is already possible using the gather() method of asyncio module, but the problem with it is that if there is any exception happens due to gathering tasks together then all the tasks after that exception will not run, we have to handle that exception and also if there are multiple exceptions in a gather then we have to deal with them one by one.

But now with the Task group, we can handle using Exception Groups to handle multiple exceptions together, and also it gives us a more structured and detailed understanding of the error.

Prerequisite

  1. Exception Handling
  2. Asyncio
  3. Exception Groups  in Python

NOTE: Task group is the latest feature available in Python 3.11, so if the user doesn’t have Python 3.11 installed this will not work.

Let’s understand using the Coding process:

Firstly we will create a simple Task group with asyncio to understand the thing, then we will mix it up with Exception Groups to see how to handle any error we might face while using the Task groups.

Importing the module:

We will only need the asyncio module of Python which comes with it so no need to install it or anything.

import asyncio

Coding a simple function using asyncio:

Here we will create a simple asyncio function that takes a number as an argument and returns the square of every number from 1 to n.

# Defining function to calculate square
async def square_number(n):
    for i in range(1, n+1):
        print("Square of ", i, "is ", i**2)
        await asyncio.sleep(0.001)

The above function takes an input n and returns the square of every number from 1 till n and after every print, it waits/goes to sleep for .001 seconds.

Creating a Task group and calling the Function:

Here we are creating a TaskGroup and giving it an alias task_group and using the create_task() method which is also new in Python 3.11.

create_task() method is used to create tasks under the roof of a Task group and as it is asynchronous they will execute concurrently.

Python3




# Defining main function
async def main():
    async with asyncio.TaskGroup() as task_group:
          # Calling square number function
        task_group.create_task(square_number(5))
        task_group.create_task(square_number(10))
  
# calling main function
asyncio.run(main())


Output:

Python Taskgroups with asyncIO

 

It is visible in the output that both tasks have been executed concurrently.

Creating another function and Handling Exceptions:

This is a simple function that returns the square root of a number to the nearest integer. We will now create this as a new task under the previously created task group.

async def square_root(n):
    print("Square root of ",n," rounded to nearest integer is ",
          round(n**.5))

Complete implementation of the above Codes:

Python3




import asyncio
  
async def square_number(n):
    for i in range(1,n+1):
        print("Square of ",i, "is ", i**2)
        await asyncio.sleep(0.001)
  
async def square_root(n):
    print("Square root of ",n," rounded to nearest integer is ",
          round(n**.5))
  
  
async def main():
    async with asyncio.TaskGroup() as task_group:
        task_group.create_task(square_number(5))
        task_group.create_task(square_root(25))
        task_group.create_task(square_root(18))
      
    print("All different tasks of task_group has executed successfully!!")
  
  
asyncio.run(main())


Output:

Python Taskgroups with asyncIO

 

Introducing an Exception to the Task group:

Now we will intentionally introduce an exception into the task group and see how we will handle it using Exception Groups

Creating another function divide to get different kinds of errors to handle. Now if we create the task_groups by passing Geeks and 10,0 both will raise different exceptions.

Python3




async def divide(num1,num2):
    if num2 == 0:
        raise Exception("Trying to Divide by Zero")
    else:
        print(num1/num2)
  
  
async def main():
    async with asyncio.TaskGroup() as task_group:
        task_group.create_task(square_number(5))
        task_group.create_task(square_root(25))
        task_group.create_task(square_root(18))
  
        # Returns Exception
        task_group.create_task(square_number("Geeks"))
        task_group.create_task(square_number('a'))
        task_group.create_task(divide(10,0))


Output:

Python Taskgroups with asyncIO

 

Here we can see a detailed and structured way of Exceptions shown to us.

Handling Exceptions generated by Tasks of Task group:

In the below code, we are using try, except blocks of error handling to handle TypeError and Exception errors.

Syntax:

try:

    # Some Code

except:

    # Executed if error in the

    # try block

Python3




async def main():
    try:
        async with asyncio.TaskGroup() as task_group:
            task_group.create_task(square_number(5))
            task_group.create_task(square_root(25))
            task_group.create_task(square_root(18))
  
            # Returns Exception
            task_group.create_task(square_number("Geeks"))
            task_group.create_task(square_number('a'))
            task_group.create_task(divide(10,0))
    except* TypeError as te:
        for errors in te.exceptions:
            print(errors)
    except* Exception as ex:
        print(ex.exceptions)
      
    print("All different tasks of task_group has \
           executed successfully!!")
  
asyncio.run(main())


Output:

Python Taskgroups with asyncIO

 

We have handled all those Exceptions using Exception Groups easily. All exceptions became part of a Single Group and we didn’t need to handle them one by one by using the traditional except keyword.

Using old asyncio.gather() to do the same thing:

asyncio.gather() function is used to run asynchronous numerous operations and when all the operations are completed we get the results.

Syntax: asyncio.gather(*aws, return_exceptions=False)

Parameters:

  • aws: It is a sequence of awaitable objects and any coroutine in this will automatically schedule as a task.
  • return_exceptions: It is False by default. If any awaitable object raise exception then it is pass on to the other tasks and other awaitable objects won’t be canceled.

Python3




import asyncio
  
async def square_number(n):
    for i in range(1,n+1):
        print("Square of ",i, "is ", i**2)
        await asyncio.sleep(0.001)
  
async def square_root(n):
    print("Square root of ",n," rounded to nearest integer is ",
          round(n**.5))
  
async def divide(num1,num2):
    if num2 == 0:
        raise Exception("Trying to Divide by Zero")
    else:
        print(num1/num2)
          
async def main():
    tasks = asyncio.gather(square_number(5),
                           square_root(16),
                           divide(15,0),
                           square_root("Geek"))
  
    await tasks
  
asyncio.run(main())


Output: Here in the output, we can see that whenever it has encountered an exception it has stopped executing, but there was another exception after that, it didn’t even reach there. But the above code in the task group it had given all the possible Exceptions in a Structured way.

Python Taskgroups with asyncIO

 

Comparing Taskgroup and asyncio.gather()

In the below code, we are comparing the output of taskgroup with exception and asyncio.gather() which we have explained above.

Python3




import asyncio
  
async def square_number(n):
    for i in range(1,n+1):
        print("Square of ",i, "is ", i**2)
        await asyncio.sleep(0.001)
  
async def square_root(n):
    print("Square root of ",n," rounded to nearest integer is ",
          round(n**.5))
  
async def divide(num1,num2):
    if num2 == 0:
        raise Exception("Trying to Divide by Zero")
    else:
        print(num1/num2)
  
async def main():
    try:
        async with asyncio.TaskGroup() as task_group:
            task_group.create_task(square_number(5))
            task_group.create_task(square_root(25))
            task_group.create_task(square_root(18))
  
                # Returns Exception
            task_group.create_task(square_number("Geeks"))
            task_group.create_task(square_number('a'))
            task_group.create_task(divide(10,0))
    except* TypeError as te:
        for errors in te.exceptions:
            print(errors)
    except* Exception as ex:
        print(ex.exceptions)
  
        print("All different tasks of task_group has \
               executed successfully!!")
  
"""Comment the Above main with Taskgroups and uncomment
    the below to see the tasks.gather()"""
  
async def gather_main():
    tasks = asyncio.gather(
        square_number(5),
        square_root(16),
        divide(15,0),
        square_root("Geek")
    )
  
    await tasks
print("==============================================")
print("Output of Taskgroup with Exception")
print("==============================================")
asyncio.run(main())
  
print("==============================================")
print("Output of asyncio.gather()")
print("==============================================")
asyncio.run(gather_main())


Output:

Python Taskgroups with asyncIO

 



Last Updated : 30 Dec, 2022
Like Article
Save Article
Previous
Next
Share your thoughts in the comments
Similar Reads