ESP32 In MicroPython: Asyncio |
Written by Harry Fairhead & Mike James | ||||||
Monday, 09 September 2024 | ||||||
Page 1 of 5 Asyncio is the prefered way to implement I/O servers and clients in Python. This extract is from Programming the ESP32 in MicroPython and shows you how asyncio works. Programming the ESP32in MicroPythonBy Harry Fairhead & Mike JamesBuy from Amazon. ContentsPreface
<ASIN:187196282X> Asyncio And ServersMicroPython has a basic implementation of the Python asyncio module. The asyncio module is very useful in providing asynchronous programming in Python and you might think that it would be exceptionally useful in low-level hardware applications as an alternative to polling or event driven approaches. In fact it isn’t quite as useful as you might expect as there are few existing asynchronous subsystems. In full Python we have a complete filing system and network connections and this means that asynchronous programming can improve efficiency by putting the processor to work while waiting for I/O operations to complete. In general the only I/O operations that are lengthy enough to involve waiting for sufficient time to get other tasks completed are WiFi related and this makes WiFi an ideal use case for the asyncio module. In this chapter we first look at the basic idea of asynchronous programming as implemented by uasyncio, the MicroPython version of asyncio, and then at how to use it to implement a server asynchronously. If you want to know more about asynchronous programming in general and asyncio in particular see Programmer’s Python: Async, ISBN:9781871962765. Coroutines and TasksAsyncio is all about sharing a single thread of execution between different tasks. A key ability of a task is that it can be suspended and resumed and this is what a coroutine is all about. In a single-threaded environment the thread has to save its current state, start or resume work on another function and restore the state when it returns to the previous function. A function that can be suspended and restarted in this way is generally called a “coroutine”. A modern MicroPython coroutine is created using the async keyword: async def myCo(): print("Hello Coroutine World") return 42 When you call myCo it doesn’t execute its code, instead it returns a coroutine object which can execute the code. The only problem is that a coroutine isn’t a runnable. You have to use features provided by uasyncio to run a coroutine. The simplest is the uasyncio.run method which creates and manages the task loop without you having to know anything about it: import uasyncio async def myCo(): print("Hello Coroutine World") return 42 myCoObject=myCo() result= uasyncio.run(myCoObject) print(result) This runs the coroutine object and displays: Hello Coroutine World 42 Instead of passing the coroutine object, the uasyncio.run call is usually written as a single action: result= uasyncio.run(myCo()) It is also important to realize that uasyncio.run runs myCo at once and the thread doesn’t return until myCo is completed. While running myCo a task loop is started and if the thread is freed it starts running any tasks queued before returning to myCo. In this sense the call to uasyncio.run is where the asynchronous part of your program starts and you can think of it as starting the asynchronous main program. AwaitAs it stands our coroutine might as well be a standard function as it doesn’t suspend and resume its operation. To suspend a coroutine you have to use the await keyword to pause the coroutine while an awaitable completes. An await suspends the awaiting program and this means it can only be used within a coroutine, i.e. the only code in Python that can be suspended and resumed. Once you have a coroutine running you can use await within it and within any coroutines it awaits. What this means is that you have to use uasyncio.run to get a first coroutine running, but after this you can use await to run other coroutines as Tasks. Most asyncio programs are organized so that there is a single uasyncio.run instruction at the top level of the program and this starts a coroutine, often called main, which then runs the rest of the asynchronous program by awaiting other coroutines. That is, a typical uasyncio program is: async def main(): call other coroutines using await uasyncio.run(main()) The call to uasyncio.run sets up the event loop as well as starting main running. You can call ordinary, i.e. non-coroutine, functions from within coroutines, but these cannot use await. Only a coroutine can use await, for example: import uasyncio async def test1(msg): print(msg) async def main(): await test1("Hello Coroutine World") uasyncio.run(main()) Notice that even though main now awaits the test1 coroutine there is no new behavior. The program would work in exactly the same way with functions replacing coroutines. The reason is that none of our coroutines actually release the main thread, they simply keep running. There are two distinct things that can occur when you await another coroutine. Some coroutines hold onto the main thread and continue to execute instructions until they finish – they are essentially synchronous coroutines. Some release the main thread while they wait for some operation to complete and only these are truly asynchronous coroutines. At the moment we only know about synchronous coroutines. |
||||||
Last Updated ( Tuesday, 10 September 2024 ) |