About TaskThread

Never use asyncio.wait or asyncio.gather again

TaskThread is a collection of tools, conventions and a base class that organizes your asyncio python programs seamlessly into hierarchical, well-organized structures.

These structures are analogical to threads and have a thread-like API.

Let’s illustrate what all this is about by an example.

A Sample Problem

Let’s consider a rather complex application which:

  • Exposes a TCP server

  • The server accepts various client connections from LAN

  • Server processes the incoming data and reconstructs data frames from packets received from individual TCP client connections

  • Processed data from each TCP client is forwarded through websockets to a final destination (say, to cloud)

  • The processed data is also written into a database

We can see there is a lot of i/o waiting and “multiplexing” going on that can get messy.

When using TaskThread, you start unravelling a problem by identifying hierarchies.

In the present case, a solution, described as a hierarchical list, could look like this:

MasterThread
    TCPServerThread
        TCPConnectionThread
            DataProcessorThread
        TCPConnectionThread
            DataProcessorThread
        ...
    WebsocketMasterThread
        WebSocketSubThread
        WebSocketSubThread
        ...
    DatabaseThread

(another kind of hierarchy to solve the problem is, of course, also possible)

MasterThread starts the TCPServerThread which then starts TCPConnectionThread s on demand.

Each TCPConnectionThread starts a DataProcessorThread which reconstructs the packets from indicidual TCPConnectionThread s.

All data flows to upwards in the tree into MasterThread which then passes it onto to WebsocketMasterThread and from there to individual WebSocketSubThread s. MasterThread passes the data also to DatabaseThread.

Let’s add this data flow to the hierarchical list:

MasterThread
    TCPServerThread: UP: data
        TCPConnectionThread: UP: data
            DataProcessorThread: IN: packets, UP: data
        TCPConnectionThread
            DataProcessorThread
        ...
    WebsocketMasterThread: IN: data
        WebSocketSubThread: IN data
        WebSocketSubThread
        ...
    DatabaseThread: IN: data

Here UP designates data going upwards in the tree, while IN shows incoming data at deeper level threads.

Notice that there is not any intercommunication within this tree that is not strictly between a parent and a child.

Threads, how?

So, how to implement such “threads” ? After all this is asyncio, not multithreading!

Well, the entities named here MasterThread, TCPServerThread, etc. are not really “threads”, but collection of asyncio tasks grouped together in a smart way - thus the name TaskThread. The intercom between the “threads” is done using asyncio queues.

Let’s take a deeper look on the tasks and queues:

DataProcessorThread receives packets (via a listening task) through a queue from TCPConnectionThread. After reconstructing some data from the packets using a task, DataProcessorThread has a task that sends the dataframes to TCPConnectionThread through another asyncio queue which TCPConnectionThread is listening with a task, etc.

These queues and listener tasks work seamlessly and are hidden from the TaskThread API user.

At the core of TaskThread philosophy lies rescheduling tasks, i.e. asyncio tasks that reschedule themselves, giving the appearance of “threads” and a thread-like API.

The only thing the API user needs to worry about, is how to initiate, re-schedule and terminate these tasks within their TaskThread implementation.

Advantages

Have you ever run into a situation where you have a complex asyncio program running tons of simultaneous tasks?

For example, you need to run asyncio.wait to “poll” several tasks to see if the tasks have finished or not and then your program’s logic is altered based on that result, creating an asynchronous mess, maybe even runaway tasks.

Well, you don’t need to touch asyncio.wait or asyncio.gather ever again, after starting to use TaskThread.

Your programs will also become naturally well-organized into threads that have separation of concerns and restricted communication - in accordance with the HIMO principle.

I hope you got all warmed up by now. Exciting, right!?

Next we will take a look at an anatomy of a TaskThread.

Installing

From PyPi simply with:

pip3 install --user task-virtualthread