Remix.run Logo
jayd16 7 hours ago

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 6 hours ago | parent [-]

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 5 hours ago | parent [-]

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.
int_19h 36 minutes ago | parent | next [-]

> 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.

throwup238 an hour ago | parent | prev | next [-]

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.

metaltyphoon 20 minutes ago | parent | prev [-]

In C# that Task is ALWAYS hot, aka scheduled to run.