<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged asynchronous-programming</title>
<link>https://coderlegion.com/tag/asynchronous-programming</link>
<description>Powered by Question2Answer</description>
<item>
<title>Async/Await in Production: The Bug That Only Appeared Under Load</title>
<link>https://coderlegion.com/16071/async-await-in-production-the-bug-that-only-appeared-under-load</link>
<description>&lt;h2 id=&quot;async-await-in-production-the-bug-that-only-showed-up-under-load&quot;&gt;Async/Await in Production: The Bug That Only Showed Up Under Load&lt;/h2&gt;
&lt;p&gt;Most people learn async/await in a clean little test project.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;await GetDataAsync();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Looks simple. Feels safe.&lt;/p&gt;
&lt;p&gt;Then you push to production, traffic hits, and async/await quietly wrecks your backend. This is a real failure pattern: thread starvation mixed with blocking async calls causing cascading latency spikes.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;it-worked-fine-locally&quot;&gt;It Worked Fine Locally&lt;/h3&gt;
&lt;p&gt;Typical .NET backend API:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ASP.NET Core&lt;/li&gt;
&lt;li&gt;SQL Server&lt;/li&gt;
&lt;li&gt;External HTTP calls&lt;/li&gt;
&lt;li&gt;Async/await everywhere (or so we thought)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Code looked like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;[HttpGet]
public IActionResult GetUser(int id)
{
    var user = _userService.GetUserAsync(id).Result;
    return Ok(user);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nothing obviously wrong if you come from a sync background.&lt;/p&gt;
&lt;p&gt;Locally? Fast. Stable. No issues.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;first-symptom-random-slow-requests&quot;&gt;First Symptom: Random Slow Requests&lt;/h3&gt;
&lt;p&gt;In production under moderate traffic:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Some requests take 200ms&lt;/li&gt;
&lt;li&gt;Others randomly spike to 5–10 seconds&lt;/li&gt;
&lt;li&gt;CPU is fine&lt;/li&gt;
&lt;li&gt;DB is fine&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Nothing obvious blocking. That's the problem.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-real-issue&quot;&gt;The Real Issue&lt;/h3&gt;
&lt;p&gt;This line is the killer:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;_userService.GetUserAsync(id).Result;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or &lt;code&gt;.GetAwaiter().GetResult()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;What happens:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Async method starts&lt;/li&gt;
&lt;li&gt;Hits an &lt;code&gt;await&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Thread frees up&lt;/li&gt;
&lt;li&gt;You immediately block waiting for the result&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now combine with ASP.NET thread pool behavior.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;thread-pool-starvation&quot;&gt;Thread Pool Starvation&lt;/h3&gt;
&lt;p&gt;Under load:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Thread pool has limited threads&lt;/li&gt;
&lt;li&gt;Each request blocks a thread waiting on async work&lt;/li&gt;
&lt;li&gt;But that async work needs threads to resume&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So threads wait for threads that are already blocked. Not a classic deadlock, but the system acts like one. Latency explodes.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-local-didn-t-catch-it&quot;&gt;Why Local Didn't Catch It&lt;/h3&gt;
&lt;p&gt;Locally: low concurrency, plenty of threads, no pressure.&lt;/p&gt;
&lt;p&gt;Production: many concurrent requests, real API latency, real DB spikes. Everything amplifies the problem.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-fix&quot;&gt;The Fix&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;[HttpGet]
public async Task&amp;lt;IActionResult&amp;gt; GetUser(int id)
{
    var user = await _userService.GetUserAsync(id);
    return Ok(user);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And inside services:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public async Task&amp;lt;User&amp;gt; GetUserAsync(int id)
{
    return await _repository.GetUserAsync(id);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Async all the way up. No exceptions.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;hidden-trap&quot;&gt;Hidden Trap&lt;/h3&gt;
&lt;p&gt;Sync over async in libraries:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;Task.Run(() =&amp;gt; _service.CallAsync()).Result
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or legacy code forcing sync wrappers. Spreads the problem silently across layers.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;another-silent-killer&quot;&gt;Another Silent Killer&lt;/h3&gt;
&lt;p&gt;Context capture:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;await SomethingAsync(); // captures context
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fix for backend services:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;await SomethingAsync().ConfigureAwait(false);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Avoids unnecessary context switching overhead.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;how-we-found-it&quot;&gt;How We Found It&lt;/h3&gt;
&lt;p&gt;We eventually saw:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Thread pool queue length spikes&lt;/li&gt;
&lt;li&gt;High blocked thread count  &lt;/li&gt;
&lt;li&gt;Requests stuck in &quot;waiting&quot; state&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After removing all &lt;code&gt;.Result&lt;/code&gt; usage:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Latency dropped&lt;/li&gt;
&lt;li&gt;Throughput roughly doubled&lt;/li&gt;
&lt;li&gt;CPU normalized&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Bottom line:&lt;/strong&gt; Never mix &lt;code&gt;.Result&lt;/code&gt; or &lt;code&gt;.Wait()&lt;/code&gt; with async code. Async has to be end to end. Thread pool starvation is silent but deadly. Production is not local.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/16071/async-await-in-production-the-bug-that-only-appeared-under-load</guid>
<pubDate>Tue, 28 Apr 2026 16:22:39 +0000</pubDate>
</item>
<item>
<title>Asynchronous Python: A Beginner’s Guide to asyncio</title>
<link>https://coderlegion.com/6407/asynchronous-python-a-beginners-guide-to-asyncio</link>
<description>&lt;p&gt;&lt;strong&gt;Howdy Folks:&lt;/strong&gt; Ever wondered about asynchronous processes in programming? Join us on a journey into the realm of asynchronous Python with a focus on the asyncio module. We’ll demystify the concepts, delve into syntax, and showcase how asyncio empowers concurrent task execution. Get ready to harness the power of asynchronous Python and elevate your programming prowess!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What is an Asynchronous process anyways?&lt;/strong&gt;&lt;br&gt;
In the world of programming, there are tasks that take varying amounts of time to complete. Some tasks may be quick, while others might require waiting for external resources, such as data from a server or user input. As developers, we often encounter situations where we need to perform multiple tasks concurrently without waiting for each one to finish before starting the next. This is where asynchronous programming comes into play, and the &lt;code&gt;asyncio&lt;/code&gt; module in Python is a powerful tool to achieve this.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Importing the asyncio Module&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Ok folks, the first step in utilizing the &lt;code&gt;asyncio&lt;/code&gt; module is to import it into our Python script. This can be done using the following line of code:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import asyncio
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This line makes all the asynchronous magic provided by the &lt;code&gt;asyncio&lt;/code&gt; module available for use in our script. easy peasy, lemon squeasy, right?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Understanding Asynchronous Tasks&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In traditional synchronous programming, tasks are executed one after the other, and the program waits for each task to complete before moving on to the next. In asynchronous programming, we can mark certain functions as asynchronous using the &lt;code&gt;async&lt;/code&gt; keyword, allowing them to run concurrently with other tasks.&lt;/p&gt;
&lt;p&gt;Let’s take a simple example to illustrate this concept, folks:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;async def task1():
    print(&quot;Wake Up started&quot;)
    await asyncio.sleep(1)
    print(&quot;Wake Up completed&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Oh, what is that? Ok, allow me to explain it: In this code, the &lt;code&gt;async def&lt;/code&gt; declaration indicates that the function &lt;code&gt;task1&lt;/code&gt; is asynchronous. The &lt;code&gt;await asyncio.sleep(1)&lt;/code&gt; line simulates a task that takes 1 second to complete. While waiting for this task, other asynchronous functions can execute.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Main Function and asyncio.gather&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Now, in order to coordinate and execute multiple asynchronous tasks, we use the &lt;code&gt;asyncio.gather&lt;/code&gt; method within a main asynchronous function.&lt;/p&gt;
&lt;p&gt;Let’s take a look at this example, champs:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;async def main():
    await asyncio.gather(task1(), task2(), task3())
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this code, the &lt;code&gt;main&lt;/code&gt; function is marked as asynchronous using &lt;code&gt;async def&lt;/code&gt;. The &lt;code&gt;asyncio.gather&lt;/code&gt; method is then used to execute the tasks &lt;code&gt;task1&lt;/code&gt;, &lt;code&gt;task2&lt;/code&gt;, and &lt;code&gt;task3&lt;/code&gt; concurrently. The &lt;code&gt;await&lt;/code&gt; keyword is used before &lt;code&gt;asyncio.gather&lt;/code&gt; to ensure that the program waits for the completion of all tasks.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bringing It All Together&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Now, let’s put everything together into a complete script:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import asyncio

async def task1():
    print(&quot;Wake Up started&quot;)
    await asyncio.sleep(1)
    print(&quot;Wake Up completed&quot;)

async def task2():
    print(&quot;Take a shower started&quot;)
    await asyncio.sleep(2)
    print(&quot;Take a shower completed&quot;)

async def task3():
    print(&quot;Have Breakfast started&quot;)
    await asyncio.sleep(4)
    print(&quot;Have Breakfast completed&quot;)

async def main():
    await asyncio.gather(task1(), task2(), task3())

asyncio.run(main())
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When you run this script, you’ll see the output reflecting the asynchronous execution of tasks. Each task begins, and while waiting for the time-delayed &lt;code&gt;sleep&lt;/code&gt; to complete, other tasks proceed concurrently.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Wrapping Up&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In this article, we explored the basics of asynchronous programming in Python using the &lt;code&gt;asyncio&lt;/code&gt; module. We learned how to mark functions as asynchronous, use the &lt;code&gt;await&lt;/code&gt; keyword to wait for asynchronous tasks, and coordinate multiple tasks using &lt;code&gt;asyncio.gather&lt;/code&gt;. Asynchronous programming enables more efficient use of resources and improves the responsiveness of applications, making it a valuable skill for developers working on various projects.&lt;/p&gt;
&lt;p&gt;I trust you found this article both educational and enjoyable. I’ve strived to simplify the content for a quick and effective learning experience. Happy coding, and may your Python endeavors always thrive in your daily life!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Enjoyed the article? Your feedback fuels more content.&lt;/li&gt;
&lt;li&gt;Share your thoughts in a comment.&lt;/li&gt;
&lt;li&gt;No time to read now? Well, Bookmark for later.&lt;/li&gt;
&lt;li&gt;If it helped, click on share &amp;amp; pass it on, dude!&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Python</category>
<guid isPermaLink="true">https://coderlegion.com/6407/asynchronous-python-a-beginners-guide-to-asyncio</guid>
<pubDate>Tue, 14 Oct 2025 05:51:37 +0000</pubDate>
</item>
<item>
<title>The Quest Continues: Porting the Word Game With AsyncSSH</title>
<link>https://coderlegion.com/2855/the-quest-continues-porting-the-word-game-with-asyncssh</link>
<description>&lt;p&gt;Last week, we reimplemented our &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/jeffrey04/leveling-up-llm-game-development-with-dspy-356c&quot;&gt;20-questions variant&lt;/a&gt;, and it received quite a bit of attention. So far we built a web experience, as well as a command line interface for the game. However, unlike the web version, the command line interface is strictly local. There’s no way to share the game with a fellow friend unless we walk them through the setup. Coincidentally, a former coworker recently told me about hosting applications over secure shell protocol in Golang. That makes me wonder, how easily can we replicate it in Python?&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/1*7iiy33yB4geyLDaTTY3zRw.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;A cute illustration from Copilot on the topic.&lt;/em&gt;&lt;/p&gt;
&lt;h4 id=&quot;the-spark-of-an-idea&quot;&gt;The Spark of an idea&lt;/h4&gt;
&lt;p&gt;The secure shell protocol (SSH) is usually what we use to log into another machine for work. Yet, there are a lot of interesting applications hosted over SSH. I remember there was one that would play back a &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/gabe565/ascii-movie&quot;&gt;Star Wars movie in ASCII art&lt;/a&gt; upon connecting successfully. Late last year, my coworker told me about &lt;a rel=&quot;nofollow&quot; href=&quot;https://terminal.shop/&quot;&gt;a coffee shop selling freshly brewed coffee&lt;/a&gt; over SSH. We both found that idea intriguing.&lt;/p&gt;
&lt;p&gt;He suspected the application was &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/charmbracelet/wish&quot;&gt;made with wish&lt;/a&gt;, a Golang library. That prompted me to see if there is an equivalent for Python, but I only found &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.paramiko.org/&quot;&gt;Paramiko&lt;/a&gt; at the time. After a quick glance at the documentation, I decided it was too much work and moved on. It does seem like it is capable of achieving the goal — serving a CLI application over SSH, though.&lt;/p&gt;
&lt;p&gt;Yes, the &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.rollingstone.com/culture/culture-features/chatgpt-hypen-em-dash-ai-writing-1235314945/&quot;&gt;em-dash is intentional&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/0*v3oPBEIrGmyfjgZB&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;Photo by Vitaly Gariev on Unsplash&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Keeping in touch as usual, and he would bring up the wish project from time-to-time. Then fast-forward to last week, after I was done with the reimplementation of our little word game, I stumbled upon another library called &lt;a rel=&quot;nofollow&quot; href=&quot;https://asyncssh.readthedocs.io/en/latest/&quot;&gt;AsyncSSH&lt;/a&gt;. Like Paramiko, it can be used to create an SSH server as well. The relatively more extensive examples also seemed helpful.&lt;/p&gt;
&lt;p&gt;After some exchanges with &lt;a rel=&quot;nofollow&quot; href=&quot;https://gemini.google.com/&quot;&gt;Gemini&lt;/a&gt;, I received a generated proof-of-concept code that looked workable. After completing and publishing the article on the game, I finally had time to properly explore the topic. Read on to join the ride together in building an interactive application served over SSH.&lt;/p&gt;
&lt;h4 id=&quot;finding-the-right-tool-paramiko-vs-asyncssh&quot;&gt;Finding the Right Tool: Paramiko vs AsyncSSH&lt;/h4&gt;
&lt;p&gt;Like we briefly mentioned previously, Paramiko and AsyncSSH are libraries we can use to build SSH Server applications (they are also excellent as SSH client libraries). Though the former garnered more stars on &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/paramiko/paramiko&quot;&gt;GitHub&lt;/a&gt;, the amount of information on how to build an SSH Server application is surprisingly sparse. From what I read, it also required quite extensive setup.&lt;/p&gt;
&lt;p&gt;The discovery of AsyncSSH was completely unintentional. It was buried deep in the search result when I was looking for a cohesive tutorial on Paramiko. While the design of the documentation site looked as if it was built in Python 2.0 era, the extensive collection of examples is extremely helpful. I particularly liked the simplicity offered by AsyncSSH in comparison.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/0*3qSBi8rTUc4s1NQF&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;Photo by Agence Olloweb on Unsplash&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Our goal for this week is a fairly simple application sporting a simple REPL-like experience. The key of this exercise is to learn, hence efficiency isn’t a deciding factor. Therefore, the simplicity and developer friendly documentation make the library a sensible choice for our project this week.&lt;/p&gt;
&lt;h4 id=&quot;two-paths-and-a-relearning&quot;&gt;Two Paths and a Relearning&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/0*yCF1_agka4PSzxN_&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;Photo by Ross Sneddon on Unsplash&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The quick proof-of-concept code I came out with the help of Gemini, by referring to &lt;a rel=&quot;nofollow&quot; href=&quot;https://asyncssh.readthedocs.io/en/latest/#simple-server-with-input&quot;&gt;the documentation&lt;/a&gt;, consists of 3 components. Firstly, we have the actual SSH Server class, subclassing asyncssh.SSHServer.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import asyncssh

class MySSHServer(asyncssh.SSHServer):
    def begin_auth(self, username):
        logger.info(&quot;logging in user:%s&quot;, username)
        return False

    def session_requested(self):
        return MyREPLSession()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This class is where we define all the authentication rules. For the purpose of this article, we are skipping all of them to maintain simplicity. Thus, we are returning False in begin_auth to indicate no authentication is needed. Do refer to &lt;a rel=&quot;nofollow&quot; href=&quot;https://asyncssh.readthedocs.io/en/latest/api.html#asyncssh.SSHServer&quot;&gt;the documentation&lt;/a&gt; for information on the topic before publishing applications in production.&lt;/p&gt;
&lt;p&gt;The MyREPLSession is a class inheriting asyncssh.SSHServerSession, which defines the actual application logic we intend to serve over the server. The skeleton is shown as below:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class MyREPLSession(asyncssh.SSHServerSession):
    _chan = None
    _buffer = &quot;&quot;

    def connection_made(self, chan: asyncssh.SSHLineEditorChannel) -&amp;gt; None:
        self._chan = chan

    def pty_requested(
        self,
        term_type: str,
        term_size: tuple[int, int, int, int],
        term_modes: dict[int, int],
    ) -&amp;gt; bool:
        logger.info(&quot;pty received&quot;)
        return True

    def shell_requested(self) -&amp;gt; bool:
        logger.info(&quot;shell received&quot;)
        return True

    def data_received(self, data: str, datatype: int | None) -&amp;gt; None:
        # TODO The actual application
        pass

    def eof_received(self):
        assert isinstance(self._chan, asyncssh.SSHLineEditorChannel)

        self._chan.exit(0)
        return False

    def signal_received(self, signal):
        assert isinstance(self._chan, asyncssh.SSHLineEditorChannel)

        self._chan.exit(0)

    def break_received(self, signal):
        assert isinstance(self._chan, asyncssh.SSHLineEditorChannel)

        self._chan.exit(0)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are a couple of key methods we need to implement. The first is the connection_made, in our setup we are expecting a asyncssh.SSHLineEditorChannel object. All the input and output through SSH would be handled by the channel object, hence it is crucial to keep a reference of it. Methods ending with the _received suffix are event handlers, and in our toy project, we simply close the channel when no more input is sent (upon EOF or on terminate/kill signals).&lt;/p&gt;
&lt;p&gt;We will discuss data_received event handler later.&lt;/p&gt;
&lt;p&gt;Lastly, we need to tie everything together through asyncssh.create_server. As implied by the library name, the function returns a coroutine object, and hence we would need to properly set up through AsyncIO. Feel free to revisit our previous article on &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/jeffrey04/asyncio-task-management-a-hands-on-scheduler-project-2e54&quot;&gt;how to write an asynchronous program&lt;/a&gt;. But for this example, we are adapting the example shown in the documentation.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/jeffrey04/asyncio-task-management-a-hands-on-scheduler-project-2e54&quot;&gt;AsyncIO Task Management: A Hands-On Scheduler Project&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import asyncio

async def main() -&amp;gt; None:
    try:
        # Ensure ssh-host key exists before running
        await asyncssh.create_server(
            MySSHServer,
            &quot;0.0.0.0&quot;,
            8022,
            server_host_keys=[&quot;./ssh-host&quot;],
            line_editor=True
        )
        print(&quot;SSH server listening on port 8022...&quot;)
    except Exception as exc:
        print(f&quot;Error starting server: {exc}&quot;)

if __name__ == &quot; __main__&quot;:
    loop = asyncio.new_event_loop()

    loop.run_until_complete(main())
    loop.run_forever()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We previously used asyncio.run to run the main coroutine, and set up a ShutdownHandler working with an exit event object to end a program. On the other hand, this approach was likely written before asyncio.run was introduced, with the manual management of event loop being the telling sign. The run_forever() here also eliminates the hack where people put an infinite loop after asyncssh.create_server.&lt;/p&gt;
&lt;p&gt;Essentially, the code snippet creates the server, where the server behaviour is defined by the MySSHServer, and we set line_editor=True to be explicit (as we are expecting user interaction in our session). Remember to create an SSH key for the application.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ssh-keygen -q -N &quot;&quot; -t ed25519 -f ./ssh-host
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As mentioned earlier, we are building a very simple REPL-like application. Just as a refresher, REPL stands for read-evaluate-print-loop, which aptly fits our case here. We read user input, get DSPy to process it with the LLM in background, print the generated response, rinse-and-repeat. The skeleton of the flow is shown as below:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def main() -&amp;gt; None:
    game_setup_and_welcome_speech()

    while True:
        match input(&quot;&amp;gt; &quot;):
            case command if command == &quot;quit&quot;:
                break
            case attempt:
                process_and_print_reply(attempt)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we can populate our data_received handler. This method is called whenever our user is sending input through the SSH session. Unfortunately, we cannot assume the data is always ending with a newline (user pressing enter). Sometimes, the handler may have accumulated a couple of questions and send them in one go. The function may also get called while the user hasn’t done typing. For this, we would have to handle both cases, as shown below:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class MyREPLSession(asyncssh.SSHServerSession):
    _buffer = &quot;&quot;

    def data_received(self, data: str, datatype: int | None) -&amp;gt; None:
        assert isinstance(self._chan, asyncssh.SSHLineEditorChannel)

        self._chan.write(f&quot;Welcome to my SSH server, {username}!\n\n&quot;)
        self._buffer += data

        lines = self._buffer.split(&quot;\n&quot;)
        for line in lines[:-1]:
            match line:
                case command if command == &quot;quit&quot;:
                    self._chan.exit(0)
                    break

                case attempt if attempt:
                    process_and_print_reply(attempt)

        self._buffer = lines[-1]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Remember we talked about handling user input and output through the SSHLineEditingChannel object we received in connection_made? Output of our application is done through its write() method, just like how we output to stdout (to be displayed on the terminal emulator) through print() usually.&lt;/p&gt;
&lt;p&gt;Let’s test the application.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/1*nzSnTP5-lU5fShc3HDLN3A.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;A seemingly stalled game session&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;While the application worked, the experience wasn’t ideal. My modest GPU took quite a bit of time to generate a response for every user input, and somehow the cursor stalled and wouldn’t move to another line whenever the user pressed enter. It somehow gave an impression of the application just died after every enter.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/0*5p_SWLXidmWYQ065&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;Photo by Josh Redd on Unsplash&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I was ready to move on, and start preparing to write, until I came across &lt;a rel=&quot;nofollow&quot; href=&quot;https://asyncssh.readthedocs.io/en/latest/#line-editing&quot;&gt;another example&lt;/a&gt; that fits the use-case. Now we have a code that looked a lot like the original skeleton.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async def handle_client(process: asyncssh.SSHServerProcess) -&amp;gt; None:
    username = process.get_extra_info(&quot;username&quot;)
    process.stdout.write(f&quot;Welcome to my SSH server, {username}!\n\n&quot;)

    game_setup_and_welcome_speech()

    while True:
        match await process.stdin.readline():
            case command if command == &quot;quit&quot;:
                break

            case attempt:
                process_and_print_reply(attempt)

    process.exit(0)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Previously we returned output through the SSHLineEditingChannel object, here we are doing it through the SSHServerProcess object. On the other hand, instead of input(), we read it through the readline() method in the stdin (an SSHReader object) property.&lt;/p&gt;
&lt;p&gt;Then, we need to remove the session_requested method from MySSHServer, and define the process_factory argument in asyncssh.create_server to point to our new coroutine.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;await asyncssh.create_server(
    MySSHServer,
    &quot;0.0.0.0&quot;,
    8022,
    server_host_keys=[&quot;./ssh-host&quot;],
    line_editor=True,
    process_factory=handle_client,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Re-run the application again, and to my surprise, it worked almost exactly like the original CLI version. For our project, the second approach definitely worked better. Nonetheless, the first attempt was also crucial, as it gives an idea on how input is being sent in a SSH session.&lt;/p&gt;
&lt;p&gt;The choice of naming the property holding a SSHReader object as stdin, and the fact it reads input through a method named readline piqued my curiosity. I knew we could reach sys.stdin to read content redirected from the previous command (via the pipe | operator), or file content via the &amp;lt; operator (in Unix-like environments). That said, I didn’t know reading user input interactively in a running application is done through stdin as well.&lt;/p&gt;
&lt;p&gt;My first experience writing a production CLI application started with PHP (very strange, yes). That was my first time learning about the relationship between the operators and stdin, stdout, and stderr through my supervisor. The scripts didn’t really need to collect input interactively, and given the similarity of PHP and C, I couldn’t find a scanf equivalent so I assumed PHP doesn’t have such feature.&lt;/p&gt;
&lt;p&gt;I was wrong.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/0*QGYcCYtRbatdkQ4B&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;Photo by Mr. Bochelly on Unsplash&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;After some exchanges with Gemini, I realized I could use &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.php.net/manual/en/function.readline.php&quot;&gt;readline()&lt;/a&gt; in PHP for the task. Which itself is similar to scanf and input in Python. Alternatively, I could also use fgets(STDIN) to do the job, just that I need to figure out a way to print a prompt prior to the input collection. On the other hand, if I redirect some content into a Python application, if I call input(), it would read the content in stdin, until it encounters a newline character. Essentially, it roughly equates&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import sys

sys.stdin.readline().strip()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Back to the topic, the toy example skipped many other aspects besides the authentication work we mentioned earlier. Unfortunately, due to the sheer scope and complexity, it is almost impossible for the developer to cover all use cases in the examples. Fortunately, the API documentation is very well-written, and I assume it could be useful for people who know more about the protocol.&lt;/p&gt;
&lt;p&gt;We are lucky to live in an era where a LLM chatbot like Gemini is readily available, to provide help in navigating the documentation. Besides that, it also serves as a valuable companion to help gathering complementary information, like how I relearned stdin. Even so, critical thinking is still crucial while evaluating the response to avoid falling victim to AI hallucination.&lt;/p&gt;
&lt;h4 id=&quot;bridging-game-and-server&quot;&gt;Bridging Game and Server&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/0*B-C31lKKu6WXm70m&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;Photo by Phil Hearing on Unsplash&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;We finally assembled all the pieces together and ported the game to be served over SSH. The concrete implementation can be found at the &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Jeffrey04/guessme-llm/&quot;&gt;GitHub repository&lt;/a&gt; for those who are interested. Both the CLI version and SSH version (the handle_client variant) are very similar, as shown below&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async def handle_client(process: asyncssh.SSHServerProcess) -&amp;gt; None:
    username = process.get_extra_info(&quot;username&quot;)
    process.stdout.write(f&quot;Welcome to my SSH server, {username}!\n\n&quot;) # vs print()

    game_setup_and_welcome_speech()
    while True:
        match await process.stdin.readline(): # vs input()
            case command if command == &quot;quit&quot;:
                break
            case command if command == &quot;end&quot; and answer is not None:
                answer = None
            case attempt:
                process_and_print_reply(attempt)

    process.exit(0)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The major difference between the two, is the use of process.stdout.write() to replace print(), and process.stdin.readline() to replace input (though a new coroutine called prompt_input is written to compose a process.stdout.write() to display prompt text).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async def prompt_input(process: asyncssh.SSHServerProcess, prompt: str) -&amp;gt; str:
    process.stdout.write(prompt)
    return await process.stdin.readline()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The REPL-like experience makes the word game a very synchronous experience. A response is only generated upon user submission of input. Therefore the await is necessary while expecting user input is needed to ensure order. Other than that, the asynchronous nature of AsyncSSH does not affect the game’s logic much. As each game session is independent, each time a new user connects, the library will run handle_client separately for each of them.&lt;/p&gt;
&lt;p&gt;Once the log message shows “SSH server is listening …” after executing ssh.py, it is ready to be connected to with the following command.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ssh -p 8022 localhost
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Considering we didn’t define any authentication methods in MySSHServer (which is definitely a bad practice), it should display a welcome message and the game is started right away.&lt;/p&gt;
&lt;p&gt;In this article, we explored two different strategies to host our little word game over SSH. The two approaches have their own pros and cons, but it does show how AsyncSSH makes the development experience quite intuitive. Even though I do not have much experience doing this in Golang with wish (the documentation situation is also quite bad there), I enjoy the time spent doing this with AsyncSSH.&lt;/p&gt;
&lt;h4 id=&quot;the-game-goes-live&quot;&gt;The Game Goes Live&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/0*yEHAu6wSvV2IeEN1&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;Photo by Alessandro Erbetta on Unsplash&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The point of the exercise is mainly on learning and discovery. If our game were developed with a text-based UI through &lt;a rel=&quot;nofollow&quot; href=&quot;https://textual.textualize.io/&quot;&gt;Textual&lt;/a&gt;, or even &lt;a rel=&quot;nofollow&quot; href=&quot;https://typer.tiangolo.com/&quot;&gt;Typer&lt;/a&gt;, some extra work is needed to ensure I/O is redirected properly.&lt;/p&gt;
&lt;p&gt;The experience of porting the library to SSH is a series of happy coincidences. It started with a spark from a discussion with my former coworker, and it led to a series of discoveries. This article is a culmination of the whole journey, sharing what I learned throughout. It turns out building an application to be served over SSH just needed a change in how I/O is done. Gemini also helped immensely throughout, deepening my understanding.&lt;/p&gt;
&lt;p&gt;We went through the whole process by configuring a server by subclassing a asyncssh.SSHServer, exploring two different ways to serve applications through process_factory and inheriting asyncssh.SSHServerSession, then getting it running via asyncssh.create_server.&lt;/p&gt;
&lt;p&gt;Hopefully this article sparks an interest and we see more interesting applications hosted over SSH.&lt;/p&gt;
&lt;p&gt;This article was written with assistance from Gemini, a large language model by Google. Gemini provided editorial help, fixing language errors and checking for article flow. However, the code and voice are entirely my own. If you’d like to collaborate or discuss job opportunities, please connect with me on &lt;a rel=&quot;nofollow&quot; href=&quot;https://linkedin.com/in/jeffrey04/&quot;&gt;LinkedIn&lt;/a&gt; or via &lt;a rel=&quot;nofollow&quot; href=&quot;https://kitfucoda.medium.com/&quot;&gt;Medium&lt;/a&gt;.&lt;/p&gt;
</description>
<category>More Libraries</category>
<guid isPermaLink="true">https://coderlegion.com/2855/the-quest-continues-porting-the-word-game-with-asyncssh</guid>
<pubDate>Sun, 04 May 2025 23:33:09 +0000</pubDate>
</item>
<item>
<title>AsyncIO Task Management: A Hands-On Scheduler Project</title>
<link>https://coderlegion.com/1756/asyncio-task-management-a-hands-on-scheduler-project</link>
<description>&lt;p&gt;We discussed Awaitables &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/jeffrey04/understanding-awaitables-coroutines-tasks-and-futures-in-python-gk7&quot;&gt;last week&lt;/a&gt;. The article covered coroutines, tasks and futures as well as a quick introduction to the event loop. Let’s build an example task management project to continue our learning journey.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/1*LlAB5imhzAROhennWMDxKg.jpeg&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;A cute cartoon generated by Microsoft Copilot on the topic&lt;/em&gt;&lt;/p&gt;
&lt;h4 id=&quot;introduction-building-our-task-scheduler&quot;&gt;&lt;strong&gt;Introduction: Building Our Task Scheduler&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;In the previous article, we mentioned briefly on event loops. Think of it as a timetable-like structure, which is executed throughout the lifetime of an application. Awaitables are then scheduled for concurrent execution within it. In recent releases, the Python developers provided more abstractions and reduced the need to manually manage event loops.&lt;/p&gt;
&lt;p&gt;In the article on &lt;a rel=&quot;nofollow&quot; href=&quot;https://kitfucoda.medium.com/writing-a-telegram-bot-in-python-866972ab63f5&quot;&gt;programming a chatbot&lt;/a&gt;, we created a new process each for the chatbot and the web application. We then call asyncio.run in each of the processes to execute the first coroutine. So let’s copy that to bootstrap our sample application:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import asyncio

async def main():
    await asyncio.sleep(5)

if __name__ == ' __main__':
    asyncio.run(main()) 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we run the program, it will run for 5 seconds through asyncio.sleep, and then exits. As mentioned last week, calling a coroutine (asyncio.sleep) would not cause it to execute. Omitting the await keyword will result in an error. This is because the coroutine object returned by asyncio.sleep(5) does not get scheduled for execution.&lt;/p&gt;
&lt;p&gt;In the snippet, calling asyncio.run creates an event loop, schedule the coroutine (main()) for execution, and then perform clean-up after completion.&lt;/p&gt;
&lt;p&gt;We are building a task management system, where we submit tasks, and then run a check for result manually. So the first thing we need is a simple user interface. I like the simplicity of repl-like (read-evaluate-parse-loop) interface, so we build one. To interact with our application, users can issue simple commands. We start with 2 commands below, by adapting from the implementation we did last week.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;dex : fetch the name of a Pokémon from the Pokédex based on the number &lt;/li&gt;
&lt;li&gt;fib : Returns the th Fibonacci number&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let’s start by implementing dex and fib, each represents an I/O bound and CPU-bound operation respectively.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async def dex(client: httpx.AsyncClient, id: int) -&amp;gt; str:
    assert isinstance(id, int)

    response = await client.get(f&quot;https://pokeapi.co/api/v2/pokemon/{id}/&quot;)

    return f&quot;The pokemon with id {id} is {response.json()['name']}&quot;

def fib(nth: int) -&amp;gt; str:
    assert isinstance(nth, int) and nth &amp;gt; 0

    result = ()

    for i in range(1, nth + 1):
        match i:
            case 1:
                result = (0,)

            case 2:
                result += (1,)

            case _:
                result = result[1:] + (sum(result),)

    assert len(result) &amp;gt; 0

    return f&quot;The {nth}th fibonacci number is {result[-1]}&quot; 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For dex, we want to reuse the httpx.AsyncClient, so we adapt our code to require it in the arguments.&lt;/p&gt;
&lt;h4 id=&quot;tasks-and-futures-in-action&quot;&gt;&lt;strong&gt;Tasks and Futures in Action&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Assembling the repl-like interface would need a little bit of work. We need to be able to parse the command entered by our users and schedule the correct task. For this, we use funcparserlib as featured in a &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/jeffrey04/how-to-parse-computer-code-advent-of-code-2024-day-3-2g84&quot;&gt;previous Advent of Code article&lt;/a&gt;. We are skipping the implementation detail for parser and lexer in this article, and only focus on the evaluation part, as shown below:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async def evaluate(
    client: httpx.AsyncClient,
    expression: Expression,
) -&amp;gt; Awaitable[str] | str:
match expression:
    case Expression(command=Command.DEX):
        return asyncio.create_task(dex(client, expression.args[0]))

    case Expression(command=Command.FIB):
        return asyncio.create_task(asyncio.to_thread(fib, expression.args[0]))

    case _:
        raise Exception(&quot;Unknown command&quot;) 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We want to enable users to check the progress of tasks later, so we return them as tasks. For dex requests, it is implemented as a coroutine, hence we just wrap it as a task, and return it. However, for fib, as it is CPU-bound, it will not benefit much from asynchronous execution. As discussed last week, we need to turn it into a future, so it can be executed as a thread of process, as shown below&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;loop = asyncio.get_running_loop()

# get the 100th Fibonacci number
future = loop.run_in_executor(None, fib, 100)

# later in the execution
result = await future 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As part of the abstraction effort, asyncio.get_running_loop() will fetch the event loop created by asyncio.run we called earlier. However, if the code snippet is executed after the main() coroutine is completed, it will result in an error. The first argument in run_in_executor expects either a ProcessPoolExecutor, or a ThreadPoolExecutor, but will use a default ThreadPoolExecutor if None is passed. For most cases, it is sufficient to just use the default, and hence we can turn it into a task instead&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fib_coro = asyncio.to_thread(fib, 100)

task = asyncio.create_task(fib_coro)

# later
result = await task
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we have the executor done, let’s assemble the repl. We use prompt-toolkit to prompt the user for command, and then it does the following operations in sequence&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Read the user entered command and arguments&lt;/li&gt;
&lt;li&gt;Parse the command and arguments, and return an Expression object&lt;/li&gt;
&lt;li&gt;Evaluate the expression object, and schedule tasks with asyncio.create_task&lt;/li&gt;
&lt;li&gt;Loop back to step 1&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The main coroutine, is now written as follows, with the corresponding steps are annotated as comments in the snippet&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async def main() -&amp;gt; None:
    t, g = TOKENIZER, grammar()
    session = PromptSession()

    tasks = []

    async with httpx.AsyncClient() as client:
        # step 4: loop
        while True:
            # step 1: read input
            line = await session.prompt_async(&quot;&amp;gt; &quot;, handle_sigint=False)

            match result := await thread_last(
                line,
                # step 2a: tokenize the input
                (tokenize, t),
                # step 2b: parse the input
                (parse, g),
                # step 3: evaluate
                (evaluate, client),
            ):
                case asyncio.Task():
                    tasks.append((line, result))
                    print(f&quot;Task {len(tasks)} is submitted&quot;)

                case _:
                    print(result)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Some notes on the snippet:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;t is for the definition of tokens (e.g, a sequence of letters is the name of a command)&lt;/li&gt;
&lt;li&gt;g is for the definition of the commands (what dex and fib means)&lt;/li&gt;
&lt;li&gt;The thread_last call in the snippet is taken from toolz. What it does is simply evaluate(client, parse(g, tokenize(t, line))) but we keep it that way to aid readability.&lt;/li&gt;
&lt;li&gt;We collect the tasks after evaluations into a simple list, we will work with it later&lt;/li&gt;
&lt;li&gt;In the match statement, we print task ID if the evaluator returns a Task, or the returned string otherwise.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So let’s recap what we have built so far. We should be able to run the program, and then issue commands to fetch Pokémon names, and calculate Fibonacci numbers. You may have noticed, we have yet to print the result on screen.&lt;/p&gt;
&lt;p&gt;On the other hand, we also know that main() is calling the evaluator to schedule tasks to the event loop with asyncio.create_task(). We also see await is used when the order of execution is crucial:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;When we perform a GET HTTP request, so we can parse the response JSON after receiving it&lt;/li&gt;
&lt;li&gt;When we are receiving a user input, as we need to pass the input to parser later&lt;/li&gt;
&lt;li&gt;We need to wait for evaluator to create a task, before we loop back to receiving another input from the user&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So what are the differences between the two, when should we use asyncio.create_task() and await? As a rule of thumb, if the order of execution does not matter, or if we only care about the outcome much later, use asyncio.create_task. If the coroutine does something that is needed later as shown above, then use await to ensure it is run as soon as possible, before we move on to the next line in code.&lt;/p&gt;
&lt;h4 id=&quot;managing-task-execution-and-displaying-results&quot;&gt;&lt;strong&gt;Managing Task Execution and Displaying Results&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Now we can start working on the tracker, and then build a dashboard to list tasks. These are the two command we want to build next:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;job : We show the result of computation, or a message indicating it is still waiting for a result&lt;/li&gt;
&lt;li&gt;dash: We draw a simple table to show some information about all submitted tasks&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Starting with the actual function again, first we have one for job&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def job(task: tuple[str, asyncio.Task[str]]) -&amp;gt; str:
    try:
        return task[-1].result()

    except asyncio.InvalidStateError:
        return &quot;Still waiting&quot; 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each entry in the task list, is a tuple of user input, and the actual Task object. So in this function, we are interested in finding the result. However, if the task is still not ready, then calling .result() will raise InvalidStateError, then we can return a message to tell users that it is still waiting.&lt;/p&gt;
&lt;p&gt;Then a dashboard to display an overview of the tasks&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def dash(tasks: Sequence[tuple[str, asyncio.Task[str]]]) -&amp;gt; str:
    result = [
        &quot;\t&quot;.join((&quot;id&quot;, f&quot;{'command':16s}&quot;, &quot;done?&quot;, &quot;result&quot;)),
        &quot;\t&quot;.join((&quot;==&quot;, f&quot;{'=' * 16}&quot;, &quot;=====&quot;, &quot;======&quot;)),
    ]

    for num, (line, task) in enumerate(tasks, 1):
        result.append(
            &quot;\t&quot;.join(
                (
                    str(num),
                    f&quot;{line:16s}&quot;,
                    str(task.done()),
                    task.done() and str(task.result())[:16] or &quot;&quot;,
                )
            )
        )

    return &quot;\n&quot;.join(result)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The function simply returns a table-like report to show if tasks are done, and the corresponding result whenever applicable.&lt;/p&gt;
&lt;p&gt;Then we need to expand the evaluator, to call the corresponding job tracking functions we just added above.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async def evaluate(
    client: httpx.AsyncClient,
    tasks: Sequence[tuple[str, asyncio.Task[str]]],
    expression: Expression,
) -&amp;gt; Awaitable[str] | str:
    match expression:
        ...

        case Expression(command=Command.JOB):
            return job(tasks[expression.args[0] - 1])

        case Expression(command=Command.DASH):
            return dash(tasks)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Previously, every time a user submits a task with either dex or fib, we return a job number. Now they can check the progress with the job command with corresponding job number, or display an overview of submitted jobs with dash.&lt;/p&gt;
&lt;h4 id=&quot;task-cancellation-and-graceful-shutdown&quot;&gt;Task Cancellation and Graceful Shutdown&lt;/h4&gt;
&lt;p&gt;We now already have a mostly complete program. While prompt-toolkit provides some handling for some signals, we can opt to do it on our own too (hence the handle_sigint=False in our session.prompt_async). For that, we start by declaring the ShutdownHandler, to gracefully shut down all scheduled unfinished tasks:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@dataclass
class ShutdownHandler:
    signal: int | None = None

    async def __call__ (self) -&amp;gt; None:
        logger.info(&quot;Shutting down tasks. signal:%s&quot;, self.signal or &quot;&quot;)

        tasks = tuple(
            task for task in asyncio.all_tasks() if task is not asyncio.current_task()
        )

        for task in tasks:
            task.cancel()

        await asyncio.gather(*tasks, return_exceptions=True)

        asyncio.get_running_loop().stop()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We are applying the __call__ dunder &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/jeffrey04/the-versatility-of-call-a-python-developers-secret-weapon-4akd&quot;&gt;discussed previously&lt;/a&gt; to avoid usage of functools.partial. In the coroutine, we are shutting down all the remaining tasks, and then stop the event loop (hence ending the asyncio.run we did in the beginning). Notice we are excluding the current coroutine so asyncio.get_running_loop().stop() will get to run.&lt;/p&gt;
&lt;p&gt;Next, we set up the shutdown handler to run, whenever the program receives certain signals from the operating system. In our example, we are handling SIGTERM, SIGHUP and SIGINT. We can then register the ShutdownHandler we just implemented to the event loop to respond to the signals we listed.&lt;/p&gt;
&lt;p&gt;Now we can proceed to add 2 more commands, namely&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;kill : Kill the job number &lt;/li&gt;
&lt;li&gt;quit: Exit the program&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The corresponding functions for both commands are shown below:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def kill(task: tuple[str, asyncio.Task[str]]) -&amp;gt; str:
    task[-1].cancel()

    return f'Task &quot;{task[0]}&quot; is killed'

def quit(shutdown_handler: Callable[..., Coroutine[None, None, None]]) -&amp;gt; str:
    asyncio.create_task(shutdown_handler())

    return &quot;Exiting&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every time the method .cancel() is called on a task, a CancelledError is raised. If the task is executing SQL statements in a transaction, this serves as an opportunity to quit the transaction, and rollback to undo changes done so far. But in some cases, we can just ignore the error and quit quietly. For instance, we can just suppress the error in our main() coroutine. While we revisit main(), we need to also ensure init() is called to set up the ShutdownHandler().&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from contextlib import suppress

def init() -&amp;gt; None:
    loop = asyncio.get_running_loop()

    for sig in (signal.SIGTERM, signal.SIGHUP, signal.SIGINT):
        handler = ShutdownHandler(sig)

        loop.add_signal_handler(sig, lambda: asyncio.create_task(handler()))

async def main() -&amp;gt; None:
    with suppress(asyncio.CancelledError):
        init()

        t, g = TOKENIZER, grammar()
        session = PromptSession()

        ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And the updated evaluator function is slightly revised to add support for the two new commands:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async def evaluate(
    client: httpx.AsyncClient,
    tasks: Sequence[tuple[str, asyncio.Task[str]]],
    shutdown_handler: Callable[..., Coroutine[None, None, None]],
    expression: Expression,
) -&amp;gt; Awaitable[str] | str:
    match expression:
        ...

        case Expression(command=Command.KILL):
            return kill(tasks[expression.args[0]] - 1)

        case Expression(command=Command.QUIT):
            return quit(shutdown_handler)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;background-tasks-and-event-management&quot;&gt;Background Tasks and Event Management&lt;/h4&gt;
&lt;p&gt;The implementation seems straightforward, as we are constantly interacting with the main() coroutine. It is always waiting for user commands, and by exiting we just terminate the infinite while loop. However, there are cases where the application would just run autonomously. For instance we have a simple application as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async def background_task():
    while True:
        await asyncio.sleep(10)

        print(&quot;Print something&quot;)

async def main():
    # send work to execute in the background
    asyncio.create_task(background_task())

    # do some work
    ultimate_answer = 42

    # hmm, we are done here

if __name__ == ' __main__':
    asyncio.run(main()) 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A similar pattern can be observed in the article where we discussed chatbot programming, where the background_task is a coroutine to consume messages from a queue. In this mini example program, it will end silently, and the first print() in background_task() will not happen, because main() ended so soon. In the past, I have seen people attaching another infinite loop at the end of main() so background_task() gets to run indefinitely.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async def main():
    ...

    # let's pretend we are doing work
    while True:
        pass
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While it seems to work, such that the application seems to continue to run. However, background_task() is not printing because the infinite loop in main() is blocking other coroutines from running. As a workaround, we could replace pass with something like await asyncio.sleep(2), but it felt like a hack and I don’t quite like it.&lt;/p&gt;
&lt;p&gt;Alternatively, we could create an asyncio.Event object, and then pass it to main(). Then we adapt the ShutdownHandler discussed previously, to receive an event object, as shown below:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@dataclass
class ShutdownHandler:
    exit_event: asyncio.Event
    signal: int | None = None

    async def __call__ (self) -&amp;gt; None:
        logger.info(&quot;Shutting down tasks. signal:%s&quot;, self.signal or &quot;&quot;)
        self.exit_event.set()

async def main(exit_event: asyncio.Event):
    loop = asyncio.get_running_loop()

    for sig in (signal.SIGTERM, signal.SIGHUP, signal.SIGINT):
        handler = ShutdownHandler(exit_event, sig)

        loop.add_signal_handler(sig, lambda: asyncio.create_task(handler()))

    # send work to execute in the background
    task = asyncio.create_task(background_task())

    # do some work
    ultimate_answer = 42

    await exit_event.wait()
    task.cancel()

if __name__ == &quot; __main__&quot;:
    asyncio.run(main(asyncio.Event()))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So now main() now receives an event, setup the ShutdownHandler, and then upon receiving from the event, cancel the background_task(). For completeness’ sake, we can also handle the CancelledError in background_task, as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async def background_task():
    try:
        while True:
            await asyncio.sleep(10)

            print(&quot;Print something&quot;)

    except asyncio.CancelledError:
        print(&quot;Bye bye&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;handling-exceptions&quot;&gt;&lt;strong&gt;Handling Exceptions&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Before we conclude, is on error handling. When we work with asyncio enough, we will notice if an awaitable running in background fails, it is not always obvious to us. Fortunately, like signals, we can also attach a handler if an exception is raised. Back to our example project, let’s implement an ExceptionHandler for the purpose.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@dataclass
class ExceptionHandler:
    shutdown_handler: ShutdownHandler

    def __call__ (self, loop, context) -&amp;gt; None:
        logger.error(&quot;Application failed. context:%s&quot;, context)

        asyncio.create_task(self.shutdown_handler())
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, add a new line to register the handler after the signal handler registration&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async def init():
    ...

    loop.set_exception_handler(ExceptionHandler(ShutdownHandler())) 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While this is a more a catch-all workaround, we should still properly handle exceptions in all our coroutines. For instance, in addition to CancelledError, we should probably also handle errors corresponding to network problems in dex. However we are keeping our example concise to avoid making this article into an epic novel.&lt;/p&gt;
&lt;h4 id=&quot;conclusion-mastering-asynchronous-task-orchestration&quot;&gt;Conclusion: Mastering Asynchronous Task Orchestration&lt;/h4&gt;
&lt;p&gt;That’s all I want to cover in this article. While we spent quite some time building the application, we still barely scratched the surface of the capability of AsyncIO. We discussed about how to start executing a coroutine, and then from there how to queue more tasks and awaitables into the event loop. We also discussed about exiting the program gracefully, and how to handle errors.&lt;/p&gt;
&lt;p&gt;I owe a lot to &lt;a rel=&quot;nofollow&quot; href=&quot;https://roguelynn.com/words/asyncio-we-did-it-wrong/&quot;&gt;this series of articles by Lynn Root&lt;/a&gt; while I was re-learning AsyncIO when rewriting &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/coolsilon/bigmeow_bot&quot;&gt;bigmeow&lt;/a&gt;, the chatbot project. Some part of the code in our example project today is adapted from her implementation. The complete project &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Jeffrey04/asyncio-post&quot;&gt;can be found at GitHub&lt;/a&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Jeffrey04/asyncio-post&quot;&gt;,&lt;/a&gt;,) with some slight changes.&lt;/p&gt;
&lt;p&gt;Thank you so much for reading this far, and I shall write again, next week.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;To ensure clarity and address potential language errors, this article received editorial assistance from a large language model. Please note that all ideas and concepts presented herein are my own. The code examples provided were not generated or edited by an LLM. If you’re interested in project collaboration or job opportunities, feel free to reach out to me via a message here on Medium or through LinkedIn for further discussion. This article was &lt;a rel=&quot;nofollow&quot; href=&quot;https://kitfucoda.medium.com/asyncio-task-management-a-hands-on-scheduler-project-bd7b7fe58c7e&quot;&gt;originally published at Medium&lt;/a&gt;.&lt;/p&gt;
</description>
<category>Adv Concepts</category>
<guid isPermaLink="true">https://coderlegion.com/1756/asyncio-task-management-a-hands-on-scheduler-project</guid>
<pubDate>Tue, 01 Apr 2025 06:46:58 +0000</pubDate>
</item>
<item>
<title>Understanding Awaitables: Coroutines, Tasks, and Futures in Python</title>
<link>https://coderlegion.com/1644/understanding-awaitables-coroutines-tasks-and-futures-in-python</link>
<description>&lt;p&gt;Previously, we explored &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/jeffrey04/how-to-write-an-asyncio-telegram-bot-in-python-4hig&quot;&gt;chatbot building with AsyncIO&lt;/a&gt;. As I am committing to publish one article a week, I am constantly on the lookout for new ideas. Then I figured it may be a good opportunity to delve deeper into asynchronous programming. The writing plan eventually expanded to a series of articles, and we are starting with the concept of Awaitables today.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/1*3uPI39CkDzqkT6OJUya19Q.jpeg&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;An illustration from copilot on the topic&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;To a lot of us, our first exposure to something resembles asynchronous programming would be learning to write GUI applications. A graphical application usually responds to user events, that may happen any time while it is running. We then attach a handler function to events we are interested in, however we do not have much control when it is executed.&lt;/p&gt;
&lt;h4 id=&quot;the-javascript-prelude-from-callbacks-to-promises&quot;&gt;The JavaScript Prelude: From Callbacks to Promises&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/0*RUsKxXI9tspcg5ee&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;Photo by Womanizer Toys on Unsplash&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I first learned programming when I was in college. The assignments were designed to train us to write code with a set of a controlled scope. I graduated in the era of web 2.0, that’s when people started to explore AJAX to add dynamic behaviour to web pages. Suddenly, despite we were plagued with inconsistencies among browser’s JavaScript implementation, webpages started to feel like a GUI application.&lt;/p&gt;
&lt;p&gt;AJAX, which is a short for Asynchronous JavaScript and XML, opens up the possibility for us to fetch data from a web server through JavaScript. With proper use, we can get a page to fetch additional information to update the page content, responding to various events defined in the document object model, DOM. Some examples of DOM events are, clicking on elements in page, elements loaded in page, or even timed events through setInterval and setTimeout.&lt;/p&gt;
&lt;p&gt;Despite having some experience with GUI application programming, but doing it in real enterprise setting feels very different. I was still too used to structured procedural programming, where code execution is often synchronous, in a mostly predictable order. However, when it comes to an enterprise setting, the requirements are constantly changing and time is often a luxury we cannot afford. Despite having good mentor, it still took me quite a while to have a basic grasp of asychronous programming with the delicate language construct at the time.&lt;/p&gt;
&lt;p&gt;Let's start with event handlers. Everything we see in a web page, is represented in a Document Object Model (DOM) tree, and each element is capable of responding to events. For instance, we can click on a button, and have it pop up an alert dialog through a code snippet below.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# In HTML markup
&amp;lt;button id=&quot;theButton&quot;&amp;gt;The button&amp;lt;/button&amp;gt;

# In JavaScript
const button = document.getElementById(&quot;theButton&quot;)
button.addEventListener((e) =&amp;gt; window.alert(&quot;Button is pressed&quot;))

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we have callbacks, which were popularized by AJAX calls. Back then, with &lt;a rel=&quot;nofollow&quot; href=&quot;https://jquery.com/&quot;&gt;jQuery&lt;/a&gt;, we could define handlers to deal with both success or failure cases. For instance, let's say we want to fetch the HTML markup of this blog (skipping error failure callback for brevity), we do&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$.ajax({
    url: &quot;https://kitfucoda.medium.com/&quot;,
    success: (data, status, xhr) =&amp;gt; { /* process data here */ }
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It is worth noticing that both the event handler and the success callback we defined earlier, can only access variables that were defined before their definition. The handler and callback would have no access to new external variables. On the other hand, the code outside the callback and handler do not have access to variables within them. Essentially, we can treat the code being executed separately, completely breaking off from the normal execution flow. Therefore, we do not have a way to tell if they are executed, or when they are done (or failed).&lt;/p&gt;
&lt;p&gt;It is also possible to combine the two together. For example, combining the two examples above, we can make the page to fetch this blog after clicking on the button.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;button.addEventListener(
    (e) =&amp;gt; $.ajax({
        url: 'https://kitfucoda.medium.com',
        success: (data, status, xhr) =&amp;gt; { /* maybe populate this somewhere */ }
    })
)

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Remember we talked about how it is not possible to know if these handlers are done executing from the outside? Yes, nesting them was the only way to ensure order. If you need to fetch my other blog &lt;a rel=&quot;nofollow&quot; href=&quot;https://cslai.coolsilon.com&quot;&gt;https://cslai.coolsilon.com&lt;/a&gt; AFTER finishing fetching this blog, you can do another $.ajax call within the success handler above. The growing nested callbacks eventually led to what people call a callback hell or pyramid of doom.&lt;/p&gt;
&lt;p&gt;I didn’t follow the development of JavaScript much, as I moved on to doing Python. However we eventually had Promise, and my first encounter with it was again through jQuery. Now, it is possible to write a sequence of AJAX calls in a chain, instead of nesting endlessly. For instance&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$.ajax({url: 'https://kitfucoda.medium.com/'})
    .then((response) =&amp;gt; {
        // do something with the response
        // Now start a new ajax
        return $.ajax({url: 'https://cslai.coolsilon.com/'})
    })
    .then((response) =&amp;gt; {
        // Do other thing with the response
    })
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No more pyramid of doom, but the callback and the code outside of it still remained separate and independent to each other.&lt;/p&gt;
&lt;h4 id=&quot;enter-asyncio-awaitables-defined&quot;&gt;Enter AsyncIO: Awaitables Defined&lt;/h4&gt;
&lt;p&gt;My exploration of asynchronous programming in Python, was just as chaotic. When I was working for a company that offers CDN service, I spent some time learning about &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.gevent.org/&quot;&gt;gevent and greenlets&lt;/a&gt;. It was practically my first introduction to the asynchronous world in the language. However, I left the company soon after that and didn’t have a chance to see it in action. Years later, I hacked a chatbot that barely worked, and also did a quick exercise &lt;a rel=&quot;nofollow&quot; href=&quot;https://cslai.coolsilon.com/2024/10/14/building-a-simple-asgi-websocket-application/&quot;&gt;building an ASGI websocket chatroom&lt;/a&gt;, without use of any frameworks. While I managed to do it, but it wasn’t a useful introduction to AsyncIO.&lt;/p&gt;
&lt;p&gt;This article, like the one I did on chatbot, is an article I wished I read back then.&lt;/p&gt;
&lt;h5 id=&quot;coroutines-the-foundation-of-awaitables&quot;&gt;Coroutines: The Foundation of Awaitables&lt;/h5&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/0*cYxirWUqp9vR5x23&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;Photo by Kelly Sikkema on Unsplash&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;As a prologue to the article I am writing next week, we cover &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.python.org/3/library/asyncio-task.html#awaitables&quot;&gt;Awaitables&lt;/a&gt; this week in the context of AsyncIO. Understanding what they are is core to understanding how to program with AsyncIO. However, before we start, let’s talk about coroutines. A coroutine is a function defined with an async keyword. Note that if a function has any async or await statement, it has to be defined as a coroutine.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# a normal coroutine
async def a_coroutine():
    return 42

# this is invalid
def this_should_be_a_coroutine():
    await some_awaitable()
    return 42
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We often write functions doing heavy IO-bound work as coroutines, such that other CPU bound computation can still take place simultaneously. To execute these coroutines, we would need an event loop. Despite the name, I prefer seeing it as a timetable like structure, a place where it accepts Awaitables (like coroutines) to be scheduled to run.&lt;/p&gt;
&lt;p&gt;Let’s slowly build a fictional project, as a tool to explore more about asyncio in this series of articles. We start by defining a coroutine that simulates I/O operations, and use &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.python-httpx.org/&quot;&gt;httpx&lt;/a&gt; to make HTTP requests. Given a national Pokédex ID, we want to return the name of the corresponding Pokémon.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async def dex(id: int) -&amp;gt; str:
    assert isinstance(id, int)

    async with httpx.AsyncClient() as client:
        response = await client.get(f&quot;https://pokeapi.co/api/v2/pokemon/{id}/&quot;)

        return f&quot;The pokemon with id {id} is {response.json()['name']}&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When a coroutine is called like how a function is called, it returns a Coroutine object. The returned object is still pending scheduling, hence the code isn’t executed at this point. If the coroutine does not get a chance to run, e.g. when the function calling the coroutine exits, dropping the reference to it.&lt;/p&gt;
&lt;p&gt;The quickest way to fix it is prefixing an await before it. However, like we discussed earlier, if a function contains an await, then it has to be defined as a coroutine with async. Therefore, going asynchronous usually means an all-in commitment. This also means porting code part-by-part to be asynchronous takes enormous effort.&lt;/p&gt;
&lt;p&gt;Using await is a way to schedule a coroutine to the event loop. The behaviour is similar to synchronous programming, it will attempt to start the execution as soon as possible, and will only execute the next line after a result is returned. But there are times we want to have more control, and that’s achieved by turning it to a task.&lt;/p&gt;
&lt;h5 id=&quot;tasks-scheduling-and-managing-coroutines&quot;&gt;Tasks: Scheduling and Managing Coroutines&lt;/h5&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/0*2nJ2XtcsYAO7xII2&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;Photo by airfocus on Unsplash&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;A task can be created, by passing in the coroutine object to asyncio.create_task. It returns a asyncio.Task object referencing the scheduled coroutine. Some control is offered through the returned Task object, for example we can check if it is done, cancel the execution, or set a callback when done.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; task = asyncio.create_task(dex(6)) 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/0*LRSH7j5Jmv3qjXGF&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;Photo by Halfcut Pokemon on Unsplash&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Unlike the await keyword, you can call asyncio.create_task even in a function, as long as an event loop is available and running. However, being an awaitable object, you can also block execution for subsequent statements with an await too, in a coroutine. However, it is typically used when you only want to revisit the state of execution later (or forget about it).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async def foo() -&amp;gt; str:
    task = asyncio.create_task(dex(104)

    # proceed with other work

    return await task 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/0*mdB_kyfuyeVbxtAf&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;Photo by Branden Skeli on Unsplash&lt;/em&gt;&lt;/p&gt;
&lt;h5 id=&quot;futures-representing-asynchronous-results&quot;&gt;Futures: Representing Asynchronous Results&lt;/h5&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/0*wq076oix0uxpnLSy&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;Photo by Zulfa Nazer on Unsplash&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Next, we have Futures, which is the parent class of the Task we just discussed. I personally find this tricky to explain, but while preparing for the article, I was informed that a future works the way a callback does (still remember?). Let’s say I have a very badly defined function to calculate nth Fibonacci number to simulate a CPU-bound operation&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def fibonacci(nth: int) -&amp;gt; int:
    assert nth &amp;gt; 0

    result = ()

    for i in count(1):
        match i:
            case 1:
                result = (0,)

            case 2:
                result += (1,)

            case _:
                result = result[1:] + (sum(result),)

    assert len(result) &amp;gt; 0

    return result[-1]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Asynchronous programming is useful in scenario with a lot of I/O operations. It ensures concurrency by making it possible for CPU-bound operations to work, while waiting for I/O. However, if a CPU-bound operation is taking forever, it would end up stalling everything else. Hence, it is advisible to throw the work to another thread or process. We can use loop.run_in_executor to create an asyncio.Future object to offload the work to another thread.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;future = (
    asyncio.get_running_loop()
        .run_in_executor(None, partial(fibonacci, 1500))
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first argument can take either a ThreadPoolExecutor or a ProcessPoolExecutor, leave it as None to use the default ThreadPoolExecutor. The second argument takes a callable, we could have rewrite fibonacci with __call__ dunder &lt;a rel=&quot;nofollow&quot; href=&quot;https://kitfucoda.medium.com/the-versatility-of-call-a-python-developers-secret-weapon-a6bff776971a&quot;&gt;as discussed in the previous article&lt;/a&gt;, but using functools.partial is fine.&lt;/p&gt;
&lt;p&gt;Alternatively we can use asyncio.to_thread which is an helpful abstraction to avoid creating a Future ourselves. However, it returns a coroutine, so it has to be awaited, or scheduled as a Task via asyncio.create_task. Also, unlike loop.run_in_executor, we do not get to choose between a ThreadPoolExecutor or ProcessPoolExecutor.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;coro = asyncio.to_thread(fibonacci, 1000)

# Schedule it for execution
asyncio.create_task(coro)

# Or await directly
await coro
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But we haven’t show how callbacks are possible with Futures. Let’s write a simpler example on this topic. Suppose we have a coroutine that has to be run concurrently in the background, streaming a file content over the internet, and we are interested in finding the line number of a specific line in it.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async def scan_huge_file(future, need_this_line):
    for line_num, line in enumerate(some_huge_file()):
        if line is need_this_line:
            future.set_result(line_num)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, when we schedule the tasks (again, we will dive deeper on this in the next article), we would have done something like this.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;loop = asyncio.get_running_loop()
future = loop.create_future()

asyncio.create_task(scan_huge_file(future, 'very critical line'))

# do other high-priority tasks
...

# when we are finally done with other tasks
number = await future 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The callback is done in scan_huge_file, through future.set_result. The function scheduling the coroutine, only checks if a line number is found, after it is done with other tasks. Of course we could have save a reference to the asyncio.create_task, and then make scan_huge_file return the line number so we can await the task, but this is just to show how a callback can be implemented.&lt;/p&gt;
&lt;p&gt;Now we know the three Awaitables, it is time to revisit what it is. As you may have guessed, it is just a term to describe these three types of objects, where we can use await to get the result.&lt;/p&gt;
&lt;h4 id=&quot;python-vs-javascript-comparison&quot;&gt;Python vs. JavaScript: Comparison&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/0*YtGBGib8QDsfSgtG&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;Photo by Oleksandr Chumak on Unsplash&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Asynchronous programming in JavaScript modelled the problem very differently, and hence we have a complete different design. We stopped at Promise just now, and we briefly discussed that because it is the root of the implementation.&lt;/p&gt;
&lt;p&gt;AsyncIO is doing a good job abstracting the event loop away from the users. For instance, we only started to call event loop methods, when we were discussing about Futures. The development experience is getting so much better that using await directly to the coroutine, or to the Task (which wraps a coroutine) should be sufficient for most cases.&lt;/p&gt;
&lt;p&gt;OTOH, event loop in JavaScript, at least in browser, is almost completely abstracted away from us.&lt;/p&gt;
&lt;p&gt;A friend came to me a few days ago, complaining how he is confused about asynchronous programming in the language. Why certain asynchronous functions were called with an await but some without. That prompted me to do a quick check on the topic, and I was quite surprised with the discovery. It turns out if I call an asynchronous function without using await, it will be scheduled in the background.&lt;/p&gt;
&lt;p&gt;The discussion session was the main inspiration to this article, sans the Python part. I started with how event handlers led to creation of callbacks, Promise, and ultimately joining the async-await family. Similar to Python, if a function contains any async or await statement in it, then it has to be defined as async.&lt;/p&gt;
&lt;p&gt;While on the surface both share similar syntax, but the underlying implementations are completely different. In Python asynchronous was designed around coroutines, while in JavaScript was through Promise. I hope by weaving examples from both language it helps to clarify doubts on the matter.&lt;/p&gt;
&lt;h4 id=&quot;looking-ahead-more-asyncio&quot;&gt;&lt;strong&gt;Looking Ahead: More AsyncIO&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/0*b7OX8Kc-FR_Rm-cg&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;Photo by Andrew Ly on Unsplash&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Originally I was about to write on schedule and manage tasks, alongside notes on error handling through a fictional project. However, after planning out the content, just Awaitables alone would be long enough for the week. Hence, we would start building a fictional project next week, and discuss the above mentioned topics. Feel free to subscribe to the email newsletter so you don’t miss it, and leave me messages if I have missed anything today.&lt;/p&gt;
&lt;p&gt;Thanks for reading, and I shall write again, next week.&lt;/p&gt;
&lt;p&gt;For transparency, I’d like to acknowledge that while the code and technical explanations presented in this article are entirely my own, I received editorial assistance from a large language model. This collaboration has helped refine the clarity and structure of my writing. If you’re interested in discussing project collaborations or job opportunities, please feel free to reach out to me &lt;a rel=&quot;nofollow&quot; href=&quot;https://kitfucoda.medium.com&quot;&gt;here on Medium&lt;/a&gt; or via &lt;a rel=&quot;nofollow&quot; href=&quot;https://linkedin.com/in/jeffrey04&quot;&gt;my LinkedIn profile&lt;/a&gt;. The article was &lt;a rel=&quot;nofollow&quot; href=&quot;https://kitfucoda.medium.com/understanding-awaitables-coroutines-tasks-and-futures-in-python-a9c12890d20d&quot;&gt;originally published to Medium&lt;/a&gt;.&lt;/p&gt;
</description>
<category>Adv Concepts</category>
<guid isPermaLink="true">https://coderlegion.com/1644/understanding-awaitables-coroutines-tasks-and-futures-in-python</guid>
<pubDate>Mon, 24 Mar 2025 18:58:34 +0000</pubDate>
</item>
</channel>
</rss>