Comment by jayd16

Comment by jayd16 20 hours ago

5 replies

C# works like this as well, no? In fact C# can (will?) run the async function on the calling thread until a yield is hit.

throwup238 20 hours ago

So do Python and Javascript. I think most languages with async/await also support noop-ing the yield if the future is already resolved. It’s only when you create a new task/promise that stuff is guaranteed to get scheduled instead of possibly running immediately.

  • amluto 18 hours ago

    I can't quite parse what you're saying.

    Python works like this:

        import asyncio
    
        async def sleepy() -> None:
            print('Sleepy started')
            await asyncio.sleep(0.25)
            print('Sleepy resumed once')
            await asyncio.sleep(0.25)
            print('Sleepy resumed and is done!')
    
    
        async def main():
            sleepy_future = sleepy()
            print('Started a sleepy')
    
            await asyncio.sleep(2)
            print('Main woke back up.  Time to await the sleepy.')
    
            await sleepy_future
    
        if __name__ == "__main__":
            asyncio.run(main())
    
    Running it does this:

        $ python3 ./silly_async.py
        Started a sleepy
        Main woke back up.  Time to await the sleepy.
        Sleepy started
        Sleepy resumed once
        Sleepy resumed and is done!
    
    So there mere act of creating a coroutine does not cause the runtime to run it. But if you explicitly create a task, it does get run:

        import asyncio
    
        async def sleepy() -> None:
            print('Sleepy started')
            await asyncio.sleep(0.25)
            print('Sleepy resumed once')
            await asyncio.sleep(0.25)
            print('Sleepy resumed and is done!')
    
    
        async def main():
            sleepy_future = sleepy()
            print('Started a sleepy')
    
            sleepy_task = asyncio.create_task(sleepy_future)
            print('The sleepy future is now in a task')
    
            await asyncio.sleep(2)
            print('Main woke back up.  Time to await the task.')
    
            await sleepy_task
    
        if __name__ == "__main__":
            asyncio.run(main())
    
        $ python3 ./silly_async.py
        Started a sleepy
        The sleepy future is now in a task
        Sleepy started
        Sleepy resumed once
        Sleepy resumed and is done!
        Main woke back up.  Time to await the task.
    
    I personally like the behavior of coroutines not running unless you tell them to run -- it makes it easier to reason about what code runs when. But I do not particularly like the way that Python obscures the difference between a future-like thing that is a coroutine and a future-like thing that is a task.
    • throwup238 15 hours ago

      That’s exactly the behavior I’m describing.

      `sleepy_future = sleepy()` creates the state machine without running anything, `create_task` actually schedules it to run via a queue, `asyncio.sleep` suspends the main task so that the newly scheduled task can run, and `await sleepy_task` either yields the main task until sleepy_task can finish, or no-ops immediately if it has already finished without yielding the main task.

      My original point is that last bit is a very common optimization in languages with async/await since if the future has already resolved, there’s no reason to suspend the current task and pay the switching overhead if the task isn’t blocked waiting for anything.

    • int_19h 14 hours ago

      > I personally like the behavior of coroutines not running unless you tell them to run -- it makes it easier to reason about what code runs when.

      In .NET the difference was known as "hot" vs "cold" tasks.

      "Hot" tasks - which is what .NET does with C# async/await - have one advantage in that they get to run any code that validates the arguments right away and fail right there at the point of the call, which is easier to debug.

      But one can argue that such validation should properly be separate from function body in the first place - in DbC terms it's the contract of the function.