1. 8
    1. 5

      Long time ago (~2 years) I had a much better experience with Trio. Possibly asyncio has gotten better, I haven’t kept up with its changes.

    2. 3

      As someone who has done very little asynchronous programming in general: does it hold true that in order to effectively make an asynchronous program all of the underlying dependencies need to support await as well? I wanted to make a script that leveraged asyncio, but it quickly fell to pieces because I was not able to await one piece of it. Would it have just made sense to rely on threading instead? Or maybe I’m completely off my rocker and it’s something else entirely…

      1. 5

        Actually in a case like that it might even make sense to combine async & threading. In python’s asyncio you can shove any sync code into a threadpool relatively easily with:

        loop = asyncio.get_running_loop()
        await loop.run_in_executor(None, sync_function)
        
      2. 5

        Any async code can call any sync code. Also any sync code can start an event loop, run async code and then stop it. The thing you have to be very aware of that you must not block the event loop with long-running or CPU intensive tasks, because only one task is running at a time and it would block every other task (and the event loop).

        It’s still probably easier than threading, because you don’t have to synchronize and lock tasks that much, because of the linear style of async/await.

        1. 3

          It’s still probably easier than threading, because you don’t have to synchronize and lock tasks that much, because of the linear style of async/await

          What is the difference? If you need to access a shared resource, how and why do you not need to wrap the operation in a semaphore/lock/mutexjust like you do with multi-threading?

          I have used eventlet and gevent before asyncio existed. To this day, i still don’t get why the language didn’t go with a similar approach to those libraries and instead chose a new keyword to divide any python program into two separated, yet mangled, parts.

          1. 1

            Between two await, only one task is running at a time, so you can know for sure nothing else will be running. To put it other way, blocks between two await are “atomic”, so you probably need fewer locks because of this. Here is an example:

            global scores
            
            def update_score():
                res = await request_score_json()
            
                json_res = res.json()
                team_score = scores["myteam"] 
                team_score += int(json_res["score"])
            
                await send_new_score()
            

            You don’t need a lock here, because during updating score, no other task is running. You control when you give back the control to the event loop.
            You would need a lock here for threads, because you have no control over when a thread is context switched.
            This is the main difference between preemptive vs cooperative multitasking.

            1. 1

              That is a lock. You put await on both instructions that perform io. The execution will wait (block) for io to return in every place of your code.

              For me, the usefulness of async APIs, lies in the possibility to start up io actions without making the whole application pause everything else and wait for io to return. Your example waits for all io to return. At that point you could just use sync code?

              1. 2

                There could be multiple concurrent update_score()s running. The await allows multiple to run on an event loop generally with one thread (vs with multiple threads to achieve concurrency in sync code).

                1. 1

                  But then is just a matter of different underlying technology, for no benefit in terms of exposed APIs for concurrency . You could replace those await prefixed lines with blocking calls and also run multiple concurrent ones in different threads of even processes. At that point, new syntax and semantics for concurreny becomes unnecessary/useless. Indeed, there are green threading libraries out there that use traditional APIs.

                  1. 1

                    This was in response to it await being a lock. Locks can block multiple tasks, but await only semantically “blocks” the caller task.

                    Regarding “why async in python when there’s green threads”: I believe it’s for improving implementation. Coroutines built with async in most languages are often stackless instead of stackful. This allows for 1) allocating all required memory for the task upfront 2) faster task switching (less state saving, just callback) and 3) lower memory overhead (no call stack, just a union of all states).

                    Python might be an exception in this regard if its generators (what it uses for stackless coroutines) is just as inefficient as the other green thread implementations.

              2. 1

                You misunderstand asyncio and the event loop. await doesn’t block, it gives back control to the event loop until the kernel notifies the event loop with epoll (or appropriate system call), and then the event loop will give back control after the point of await. It is definitely not a lock, but you can call it a synchronization point.

                1. 1

                  I am not sure what your definition of blocking is. But whatever you put after an await keyword, will not be executed until the awaited operation is complete. Calling it ‘giving back control’ or ‘getting notified’ does not change a thing. Your program will indeed wait for io right there. Which supposedly is what async programming should overcome.

                  Awaiting for all io, results in a program with the same order of execution of one with blocking io. The point is to do other things while io is being performed. Possibly more io calls.

                  I am not trying to be negative. I am genuinely curious about what is the fundamental practical difference other than being lighter weight.

                  Eventlet and gevent existed years before asyncio and both have APIs fully compatible with threading, multiprocess, semaphore, etc. In the standard library.

      3. 4

        Async code can call sync code.

        Sync code has to do some work to call async code – await in a non-async context is a syntax error, so you have to manually set up an async event loop to run any async functions.

        More importantly, async generally only helps if your problem is I/O bound and if you don’t mind the potential downsides of cooperative multitasking (which under the right load patterns can sometimes make your whole app slower than it would have been with other approaches).

    3. 2

      I’ve tried to write my last scraper with async playwright, but losing the ability to comfortable breakpoint() and explore what to do next makes programming python a huuuuuuuge pain.