Running tasks in sequence with Chain

Today we’re going to talk about something that might seem like magic at first: running tasks in sequence with Chain. Relax, it’s all good, because this tutorial is here to demystify the process for you and make it as easy as pie (or maybe more like pizza).

First things first, let’s define what we mean by “tasks” in this context.In Python, a task can be any function or coroutine that performs some sort of operation. For example:

# This script defines two functions, print_hello and print_world, which simply print out "Hello!" and "World!" respectively.

import time

def print_hello():
    print("Hello!") # This function prints out "Hello!"

def print_world():
    print("World!") # This function prints out "World!"

async def run_tasks():
    await print_hello() # This line waits for the print_hello function to finish executing before moving on to the next line
    await print_world() # This line waits for the print_world function to finish executing before moving on to the next line

In this example, we have two functions (print_hello and print_world) that simply print out some text. We’ve also created an async def run_tasks which calls both of these functions using the `await` keyword.

But what if you want to execute multiple tasks in a specific order? That’s where Chain comes in! The Chain class is part of Python’s built-in library and allows us to chain together asynchronous tasks sequentially. Here’s an example:

# Import necessary libraries
import time # Import the time library to use for delaying the execution of tasks
import asyncio # Import the asyncio library to use for asynchronous programming
from concurrent.futures import ThreadPoolExecutor, as_completed # Import the ThreadPoolExecutor and as_completed functions from the concurrent.futures library
from typing import List # Import the List type from the typing library

# Define two functions to print "Hello!" and "World!" respectively
def print_hello():
    print("Hello!")
    
def print_world():
    print("World!")
    
# Define a class called Chain that allows us to chain together asynchronous tasks sequentially
class Chain:
    def __init__(self, tasks: List[asyncio.Task]): # Initialize the class with a list of tasks
        self.tasks = tasks # Assign the list of tasks to the class attribute "tasks"
        
    async def __call__(self): # Define the __call__ method to execute the tasks in the list sequentially
        for task in self.tasks:
            await task() # Use the await keyword to execute each task in the list sequentially
            
# Define a function to run a list of tasks asynchronously
async def run_tasks(task_list: List[asyncio.Task]):
    chain = Chain(task_list) # Create a new Chain object with the list of tasks
    for task in task_list:
        await asyncio.create_task(chain) # Use the create_task function to start the first task in the list and pass it to the Chain object
    
# Define the main function to execute the tasks
async def main():
    with ThreadPoolExecutor() as executor: # Use the ThreadPoolExecutor to create a pool of threads
        task_list = [executor.submit(run_tasks, [print_hello]), executor.submit(run_tasks, [print_world])] # Create two lists of tasks and pass them to the run_tasks function using the submit function from concurrent.futures
    await asyncio.gather(*task_list) # Use the gather function to wait for all tasks in the task_list to finish executing
    
# Call the main function to start the execution of tasks
asyncio.run(main())

In this example, we’ve created a new `run_tasks` function that takes a list of asynchronous tasks (in our case, the print functions). We then create a Chain object with those tasks and start the first task in each list using the `asyncio.create_task` function.

The `ThreadPoolExecutor` is used to run multiple tasks concurrently on separate threads. This can help improve performance if you have many tasks that need to be executed sequentially but don’t want to block your main thread while waiting for them to finish.

Finally, we use the `asyncio.gather` function to wait for all of our tasks in task_list to finish executing. This is similar to using the `await asyncio.wait([task1, task2])` syntax, but with a few key differences:

– `asyncio.gather` can handle both synchronous and asynchronous tasks (as long as they are awaitable), whereas `asyncio.wait` only handles asynchronous tasks.
– `asyncio.gather` returns an object that you can use to check the status of each task, whereas `asyncio.wait` simply waits for all tasks to finish and then returns a list of their results (if they have any).

Running tasks in sequence with Chain is easy as pie (or maybe more like pizza) using Python’s built-in library. Give it a try and let us know how it goes!

SICORPS