Making run method non-blocking #252
Replies: 4 comments 4 replies
-
Without seeing the code that you wrote I don't fully understand what you are doing. The Examples: Using async def main():
await app.start_server() # blocks
asyncio.run(main()) Using async def main():
task = asyncio.create_task(app.start_server()) # doesn't block
# do other stuff here
asyncio.run(main()) See the documentation for the arguments you can pass. |
Beta Was this translation helpful? Give feedback.
-
webserver.py server = Microdot()
# Route definitions...
async def start():
server.run main.py async def main():
# Run my method for setting up the wifi
# noinspection PyAsyncCall (PyCharm shows a warning about create_task not being awaited)
asyncio.create_task(webserver.start())
# Other code that needs to run alongside the server
asyncio.run(main()) Now what I was proposing is to make the def run(self, host='0.0.0.0', port=5000, debug=False, ssl=None, non_blocking=False):
if non_blocking:
# noinspection PyAsyncCall
asyncio.create_task(self.start_server(host=host, port=port, debug=debug,
ssl=ssl)) # pragma: no cover
else:
asyncio.run(self.start_server(host=host, port=port, debug=debug,
ssl=ssl)) # pragma: no cover This would allow me to change my code like this: webserver.py server = Microdot()
# Route definitions...
def start():
server.run(non_blocking=True) main.py async def main():
# Run my method for setting up the wifi
webserver.start()
# Other code that needs to run alongside the server
asyncio.run(main()) Now as I was saying this is not biggie, if I really want to make the code in main.py as clean as I shown above I could do something like this workaround: webserver.py server = Microdot()
# Route definitions...
async def run_non_blocking():
server.run
def start()
# noinspection PyAsyncCall
asyncio.create_task(run_non_blocking()): And my main.py could look the same as it would with the proposed Microdot change. I could also do this, which would be be better: webserver.py server = Microdot()
# Route definitions...
def start()
# noinspection PyAsyncCall
asyncio.create_task(server.start_server()): This would also make my main.py look the same as with the proposed Microdot change. The point is, I feel that adding a parameter to the run method would make all of this a bit cleaner and easier to read. It is less "workaroundy" than the last two options I proposed and it is easier to read than the first method of how I have to do it right now. To me it feels more self descriptive and it also moves the IDE warning away from my code, which could be preferred by other Microdot users, beginners would also have one less warning to worry about. Technically I could also do Adding a parameter like this would not break or change anything for the existing Microdot applications either. |
Beta Was this translation helpful? Give feedback.
-
I'm not sure you realize this, but PyCharm's static analyzer is wrong. It is perfectly fine to call In any case, as I said above, you cannot use async def start():
server.run What you are proposing is really not at all different to what I suggested. Here is your suggestion again: def run(self, host='0.0.0.0', port=5000, debug=False, ssl=None, non_blocking=False):
if non_blocking:
# noinspection PyAsyncCall
asyncio.create_task(self.start_server(host=host, port=port, debug=debug,
ssl=ssl)) # pragma: no cover
else:
asyncio.run(self.start_server(host=host, port=port, debug=debug,
ssl=ssl)) # pragma: no cover The only difference that I can see is that you are suggesting to move the code that PyCharm does not like inside this package instead of being in your own application. Calling |
Beta Was this translation helpful? Give feedback.
-
I do realize that PyCharm is wrong and that is why I am suppressing the warning without worrying about it. As far as running asynchronous functions from a synchronous context, let me try to explain why I think that can be a valid approach. I am working in a team, there is several of us writing code for the application we are developing. Each one of us is responsible for a different part of the code. We write everything in classes and we have a conventions for methods of these classes. Pretty much everything where we start some process has a The person putting things together in the controller of the app does not need to know what the web server I make does in the background, he does not need to understand it. For him it is enough to know that And given Microdot is meant to be a lightweight framework and (correct me if I got this impression wrong) is more intended for MicroPython, it doesn't make much sense to me to make the run method blocking. Microdot is an amazing framework, it is the best webserver for MicroPython I have been able to find, but finding out that the main way that is presented in the documentation to run the server is blocking was very surprising. I understand your point about explicit being better than implicit, but in this case coming to Microdot with the expectation that it is "an asynchronous framework" that can run on micro controllers after reading the documentation, I would assume that it is fully asynchronous from the get go. That I just run the server and it works asynchronously right away allowing me to get the most out of the limited processing power I have in the micro controller. Maybe it is just a question of taste and preference. But reading the documentation and seeing Microdot is an "asynchronous framework..." and then seeing "The run method starts... This method blocks while it waits for connections from clients." in the next paragraph was odd. Finally, please don't take what I say in a negative way. I do not mean to insult your work or waste your time arguing about something in your open source work not being to my taste. As I have said already, Microdot is an amazing feature rich web framework and I am truly grateful to you for making it an open source framework that all of us can use. I have been yet to find a MicroPython open source documentation for a framework that would be as well written as yours is. My reason for starting this discussion was that I truly believe it would be of benefit to the users if they would be able to run Microdot asynchronously by default, or at least making it a bit easier to do so by making the I understand Microdot may have been written at a time when asyncio wasn't available on MicroPython and that the synchronous method might be for compatibility or consistence. But I still believe that adding a parameter to the run method to allow Microdot to be ran asynchronously directly and be non-blocking would make it easier to make the most of asyncio while using Microdot. All I ask is for you to consider my suggestion. Is it really the better way to make the default server starting method of this server be blocking if Microdot is pretty much the only reasonable MicroPython web server option? |
Beta Was this translation helpful? Give feedback.
-
I am working with Microdot on an ESP32 S2. I am building a web server for a control unit, which will be running multiple tasks asynchronously. In order to make this possible I have to run the web server code through an async start coroutine at the end of which I call the run method. Then in the main file I have to run start with asyncio.create_task, which is something PyCharm is apparently not too happy about because the coroutine is not awaited.
Now this is in no way a big deal preventing me from doing something, it works like this and I can use the
# noinspection PyAsyncCall
to suppress the warning and I can hide the create_task in another method to keep the main file clean.But I want to ask. Is there any specific reason for letting the run method be blocking by using asyncio.run instead of asyncio.create_task in microdot.py?
I understand that having the code be blocking makes things simpler, possibly reducing the possibility of someone miss-managing the asynchronous aspect, and it also keeps in line with the behavior of other web frameworks like Flask. But given Microdot is tailored to to be lightweight and MicroPython friendly, perhaps adding an option to run the code in a non-blocking way could be beneficial?
Maybe adding a parameter to the run method or defining another method, something like run_non_blocking. Which would keep the current blocking run behavior as the default or preferred way to run the server, but it would also make it a bit easier to integrate Microdot into big asynchronous applications.
It's just an idea, something that makes sense for me in my use cases, wanted to ask your opinion on adding something like this to Microdot.
Beta Was this translation helpful? Give feedback.
All reactions