Asyncio without function colouring

I’m not interested in testing it without fully understanding the intended behavior I’m meant to be testing. Above, when you described the behavior, you described several things that are breaking changes, but are now saying all code will work unchanged.

If you want people to take it seriously, there has to be a clearer picture of what’s being proposed. An implementation is not a specification.

1 Like

Yes, and no. I listed the things you’ve picked up on because they could be incompatible changes. Any change can be an incompatible one, the art is judging the level of impact. I’m glad you’ve picked up on them. I do not have a good feel for the existing async landscape, so thank you for the feedback.

On inline generators ((x for x in things)) my own experience, is that of surprise. ((x for x in things,2,3)is a tuple, (x for x in things,2)is a tuple, (x for x in things,) is a tuple, but (x for x in things)is a generator. Inline async generators, for me, is a whole extra level of gotcha. That being said, as async generators still work, and, if this incompatibility is a problem, the code generator could be adapted to make an async generator in this case.

On your library idea - I think it could work in many cases, maybe the majority, but quite a few libraries, wouldn’t be fixed (ones with call-backs passed in, for example - is the callback async or not?). I also feel it’s a work-round for only one part of the problem.

On 3rd party event loops - I have no feel how much this is done, and whether they have their own Future classes. Do you have an example in mind?

Could you create a similar WASM demo so that anyone interested can try it out without compiling?

I don’t support this change at all - much for the reasons already posted by others.

I am writing , though, to remind that in many cases there are suitable workarounds to many of the things you want to achieve as motivation: enabling async code in special “dunder” methods such as `_setattr_` (I can imagine a pass-through setting to a network-baked object being convenient), or mainly, to avoid writing duplicate intermediate code so that one call-path composed of pure “async def” functions.

(As for properties, they work with async-def functions out of the box)

One workaround is when calling sync code from an async context, that would downstream have to await things (become async again), to take note of the current running loop, call the synchronous code in a thread, and then, when calling async again, schedule the async code in the original loop (and synchronously wait for it in the sync-thread).

That is the approach I take in my “extraasync” packages and its `sync_to_async` and `async_to_sync` function pairs, and you would most welcome to use and or collaborate with it.

Example of code using an async `_setattr_`:

import asyncio

from extraasync import async_to_sync, sync_to_async

class A:
    def __setattr__(self, attr, value):
        async def set():
            await asyncio.sleep(1)
            super(A, self).__setattr__(attr, value)
        sync_to_async(set)

        
def trampoline(i):
    a = A()
    print(f"starting set to {i}")
    a.b = i
    print(a.b)
    
    
async def main():
    t1 = async_to_sync(trampoline, (1,))
    t2 = async_to_sync(trampoline, (2,))
    await asyncio.sleep(0.5)
    print("middle way")
    await asyncio.gather(t1, t2)

    
asyncio.run(main())

The only thing on the writer perspective is that when calling sync code from async, if one ever intend to do async inside that call, it has to be done through the `async_to_sync` bridge.
If the code calling `sync_to_async` doesn’t perceive that it is in a context started in this way, it will spawn an event loop for the current thread instead (which will be reused for further sync_to_async calls)

2 Likes

No, all of those things you say to be a tuple are syntax errors. A generator comprehension has to be surrounded by ().
((x for x in y),2,3), ((x for x in y),) - these are tuples.