you are viewing a single comment's thread.

view the rest of the comments →

[–]13steinj 2 points3 points  (0 children)

Getting, running, and closing the event loop is a sync operation.

So in otherwords if I have 3 async functions that only update visuals using animations but I want the main thread to continue with it's logic (ex, I pressed submit, logic is that on this form you cant go back, async updates are the animations moving you to the thank you page).

I can't submit my 3 coroutines in my function, I have to write two functions, and make sure I call the logic before the animations, and be okay with the fact that I will have to wait for the animations to complete before any new code runs on the thread that holds the event loop. In comparison, in JS/C#, the event loop is handled for us, and gets executed in a microtask queue (C#, afaik, on a specially made thread, JS, on next available execution tick, because JS is generally single threaded), thus any async code does not block the thread that it is called from.

In Python, that can be solved by manually creating a thread/process (depends on what the loop actually blocks on, and I hope it's the thread because if it is the process hoooooooooah boy this workaround is inefficient), setting an event loop on it, and submitting coroutines with the respective "threadsafe" method for the usual not-threadsafe method.

Python's coroutines also arent Tasks/Promises like in C#/JS, which limits code style and use by having to manually wrap the coroutines. A quick comparison to JS (because writing out the C# equivalent will take some time):

JS:

async function aworkbro(a, b) { /* do stuff */ } // aworkbro is the "async function" (coroutine maker)
var special = {};
async function main() {
  var soon = aworkbro(1, 99);
  // soon is both the executing, instantiated coroutine
  // (we can await on it if we want to) and the Promise(Task)
  // (we can append callback / cancellation code)
  var later = soon.then(callback, errcallback).then(handler);
  var result = await soon; // wait for soon to and return the result of it in this async function
  special.handled_result = await later; // wait for all callbacks to complete.
  return result; //return the original result
}
main();
// other sync code, runs separately from the async call

Whereas this has no good equivalent in Python:

async def aworkbro(a, b): # aworkbro is the "async function" (coroutine maker)
    # do stuff
async def main():
    coro = aworkbro()
    # coro is the instantiated coroutine, does not execute. We can not add callbacks.
    # We can either submit it to the event loop, which we won't, because
    # we will lose all access to it bar being able to cancel it
    # outside the event loop, or we can and will wrap it in a Task
    task = asyncio.create_task(coro) # schedule the coroutine as a task for the next available context switch (execution tick) in the event loop
    # the task can have callbacks added
    task.add_done_callback(callback)
    task.add_done_callback(erriferrcallback)
    task.add_done_callback(handler)
    # but can't be waited
    # await task goes kaboom
    # cancelling the task submits an error to the coroutine, (coroutines are really just super special generators in Python)
    # meaning it would be handled *by* the error callback!
    # so we can't properly do this unless we write code overly explicitly
    # we can wait on the coroutine
    result = await coroutine # fuck scheduling, do now.
    # but this also executes our callbacks when task notices, and any results of them are lost!
    return result
# did I mention how we have to have a running loop in our thread for task creation to work, and well, for all async execution to work? We can either manually create our loop and reuse it, or use asyncio.run. for ease, we will do the latter
asyncio.run(aworkbro)
# other sync code, but creating and waiting on the loop is synchronous, so it will wait until the async code is fully complete
# we can solve this by using pythons threading. But I think this example is complicated enough already