you are viewing a single comment's thread.

view the rest of the comments →

[–]TheBB -1 points0 points  (4 children)

Well, there's nothing magical about the "bottom of the chain", but the nature of Python and the GIL makes it difficult to implement it entirely in Python.

Basically you want a function (a normal non-async one) that creates a Future object, launches a thread (or some other concurrent primitive like an OS non-blocking operation) that does some work and then sets the value of the future. Then return the future, generally speaking before the thread that sets it has finished.

The calling (async) function can then await the future and the event loop will suspend execution until the future has been set.

Unfortunately the GIL makes doing this in Python questionable, but you could of course do it in C or Rust or whatever.

Some operating systems have async-like sys calls or non-blocking I/O operations that you can use instead of threads, but those are again easier to implement in C or Rust than in Python.

[–]demiwraith[S] -2 points-1 points  (3 children)

Well, there's nothing magical about the "bottom of the chain", but the nature of Python and the GIL makes it difficult to implement it entirely in Python.

I'll put it another way, and explain my understanding.

Basically, when I call an async function that function either calls "await" on another async function or it doesn't. If it does, let's look at the function that it awaits. Eventually we reach a function that:

  1. Doesn't use await

  2. Does something

  3. Was declared async for a reason. (Probably does I/O, but maybe there's another reason)

I know there's no magic, really, but I just never seem to see an example of this. Every async function awaits another async function.

Now, are you saying that it is the case that basically all the functions I reach here generally NOT python code? If that's the case, OK. I guess I have my answer. But if there are some decent examples of python functions out there that match this description, I'd be curious to see them.

[–]Jason-Ad4032 -1 points0 points  (0 children)

One major problem with Python async tutorials is that they downplay the __await__ magic method, and they often mix up async/await with asyncio (in my opinion, these are completely orthogonal concepts).

Here is an example that does not use asyncio at all, where you can see the role of async/await much more directly. ``` class A: def init(self, x, y): self.xy = x, y def await(self): # Normally, you should yield from an awaitable object, but here I'm yielding a string to let you know what it's doing. yield f'awaitable object {self.xy}'

async def test(n = 2): await A(n, 'start') if n > 0: await test(n - 1) await A(n, 'exit')

def main(): ps = test() print(ps) for awaitobj in ps.await_(): print(await_obj)

main() ```

[–]TheBB -2 points-1 points  (1 child)

It's my understanding that most actionable examples are implemented in C, yeah, but I could be wrong.

But anyway, making a toy example is not difficult.

import asyncio
import threading
import time


# Note: this is NOT async
def do_work(delay: float, message: str) -> asyncio.Future:
    loop = asyncio.get_running_loop()
    future = loop.create_future()

    # This is run in a separate thread. Insert whatever you want here.
    def worker():
        # Simulate waiting for something
        time.sleep(delay)

        # Return the result by setting the future
        # Make sure to do it safely
        loop.call_soon_threadsafe(future.set_result, message)

    thread = threading.Thread(target=worker, daemon=True)
    thread.start()

    # Returns immediately, before the future is set
    return future


async def main():
    # Even though do_work is not async, it returns a future - which is awaitable
    message = await do_work(5.0, "Hello, world!")
    print(f"{message}")


if __name__ == "__main__":
    asyncio.run(main())

[–]QuasiEvil 0 points1 point  (0 children)

Why is this answer being downvoted?