6 ms·
I think the issue is more like this: 1. You jump into a huge codebase and find a very useful function you want to use in your code (it uses asyncio) 2. Your c
by brrrrrm 4y ago
I think the issue is more like this:
1. You jump into a huge codebase and find a very useful function you want to use in your code (it uses asyncio)
2. Your code doesn't use asyncio, so you start converting functions to be async (or else you can't use the `await` keyword)
3. It turns out your function is called by a bunch of different users, some of which do not use the asyncio runner
4. You end up having several meetings with upstream teams asking them to use the asyncio runner
5. A single team says "no"
6. Scrap the project and just rewrite that original function you found synchronously
- quietbritishjim 4y agoI guess I was focusing more on the CPU task problem mentioned at the top. But you're talking about blocking IO - more specifically, blocking IO that you want to convert to async (not just run in a worker thread using the thread-to-async API I mentioned). What you've written is true, but "infects" a smaller proportion of the code than you'd expect, in my experience. In fact, the better organised the program is, the less needs to change when converting to async. For example, reading from a connection your top-level async function would often be something like this: async def read_and_handle_messages(connection): while True: next_message_bytes = await connection.get_next_message() next_message_parsed = my_parser.parse(next_message_bytes) my_message_handler(next_message_parsed) All the application-specific code is in the parser and the message handler but neither of those are async. (If you need to send a response, your message handler could post a message to an async queue that is read by a separate writer task.) In your hypothetical scenario, you'd maybe write two a wrapper functions, one for blocking and one for async, which both read the message, parse it and handle it. But that only needs to be two versions of a three line function while all the subtantial code is completely shared. I find that there's very little actual async code in async programs that I write, even with no blocking IO historical baggage. Admittedly, that's partly because I organise programs to reach that goal, but it actually works out for the best because all the async-task lifecycle management ends up in one place, which makes following overall program flow particularly easy.
- gpderetta 4y agoDepending on the details you can often call an async function from a sync function, by just running the event loop, it is just a few lines of code. The problem is that the event loop is not reentrant [1], so if your sync function is being called from an async function, things do not work. Why would you ever do this you might think? Well, asyncio might be an implementation detail of whatever environment you are using. Jupyiter or ipython for example. [1] there are... hacks to make this sort of work of course.
- nineplay 4y ago> there are... hacks to make this sort of work of course. The python motto.
- gpderetta 4y agoMonkey patch like there is no tomorrow (so you do not have to maintain it)!
- quietbritishjim 4y agoAnother possibility, if it's a matter of passing callback parameter that must be sync where you wish it could be an async function, is that the callback can be the `put_nowait()` method of an async queue. You can then have a background async task that pulls from that queue and does async stuff. This works well if the callback is just there to notify you about a result (which is the most common case in fairness) but doesn't really work if the callback is meant to synchronously determine and return a result that is then used within the function that's calling the callback.
- gpderetta 4y agoYes, been there done that :). You can spawn a whole background thread running its own event loop, and post the async function with run-coroutine_threadsafe, the wait on the returned concurrent.future. But it feels wrong having to spawn a thread and event loop just for that.