10 ms·
Unravelling `Async for` Loops
- benatkin 5y agoI love for await and async for but I can't seem to figure out whether it's going to be easy to understand for beginning programmers. Any thoughts?
- ekzhang 5y agoIf you're looking for alternate ways of designing an asynchronous coroutine API, Zig (https://ziglang.org/ https://ziglang.org/) and Go (https://golang.org/ https://golang.org/) have different implementations which might be worth reading into. Both avoid special syntax in the common beginner case, until you know enough to correctly write concurrent code (then you need async in Zig, and goroutines in Go). :)
- enragedcacti 5y agoas someone who has written a lot of python but doesn't really work in the backend/services world, async itself felt like a huge mess to figure out when I was exposed to it while mucking around in FastAPI internals to prove out an idea. This was mainly because of the infectious nature of async and the fact that the callback function I was passing up had to be synchronous while also calling async functions while the entire thing was running in an async loop. That meant I had to add a dependency just to call a damn function https://pypi.org/project/nest-asyncio/ https://pypi.org/project/nest-asyncio/. Rant over. `for await` and `async for` to me seem like a fairly natural extension once you understand async. I think the problem comes in where once you run into async the first time you are forced to fully grasp it (and potentially rewrite a ton of your code) to move forward which is really counter to the rest of the Python learning experience. [1] http://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/ http://journal.stuffwithstuff.com/2015/02/01/what-color-is-y...
- option_greek 5y agoThe infectious nature is crazy. They also jump between boundaries of libraries and projects through dependencies. Thanks to async, most libraries in Rust now force addition of tokio which in turn convert more libraries into async. There should be some kind of compatibility layer that lets non async functions call async without jumping through hoops. As it stands async is more infectious than T-virus and Covid.
- knuthsat 5y agoThe infectious nature is just how things work with types. Imagine if you had to annotate a function with IO every time you do something that reaches for something else outside of the program. You would either design things differently so that IO usage is minimized or you would make everything infected by IO. With Async, people are still figuring things out. I've encountered a bunch of code bases that are polluted with async when a better design could have removed it completely.
- pharmakom 5y agoI don’t really see how “infectious” a sync can be solved in a reasonable way. The function is async, can we really pretend it’s not? If it must be used in a sync context then it can be turned into a blocking call explicitly. This is not possible in JS so I can see the argument there, although they are now making the top level async too!
- jerf 5y agoWell, "easy" is a relative term, but I would say with good certainty that it is going to be a barrier for a lot of people, because it forms an inner platform with the host language: https://infogalactic.com/info/Inner-platform_effect https://infogalactic.com/info/Inner-platform_effect That is, there's the way to run one statement, then the next in the synchronously-colored portion of the language: oneStatement() thenTheNext() then there's the way to run one statement, then the next in the inner-platform asynchronously-colored portion of the language: await oneStatement().andThen(theNext) Or whatever spelling you locally like. One way to write the synchronously-colored for loop, one way to write the asynchronously-colored for loop, different ways to branch on error, different ways to handle exceptions, etc. This does not, on its own, mean "async" is bad. It is a complicated decision with many pieces. But this particular aspect of it means that there will always be a barrier to a programmer encountering this for the first time, simply by virtue of adding a new dimension they have to constantly account for to constructs that they're still getting a handle on. (Having languages that don't color this code different has its own tradeoffs, and its own challenges. One of which is precisely because there isn't the separate color in the language itself, it's much easier to blunder into problems without even realizing it! People posting "why is this code broken?" to /r/golang, getting the answer "it's a race condition", and then the poster replies back with basically "what's a race condition?" is at least an every-couple-of-months sort of thing. I don't think there's an easy way to introduce concurrency to new programmers, honestly.)
- daenz 5y agoIn my opinion, async is such a mistake because it exposes a complicated abstraction to developers that could (and should!) be totally hidden by the runtime. Async "feels" cool and powerful because you get to learn about the intricacies of concurrency via coroutines, but at the end of the day, you just produce a tangled cobweb of difficult-to-follow code that is absolutely infested with explicit references to the concurrency. And it's so easy to get wrong. Golang gets it right with goroutines. Languages should have extremely light and performant concurrency that efficiently map to OS threads. And the developer should only care about synchronization, not personally accounting for every possible context switch.
- geofft 5y agoThe problem is that that completely hiding it in the runtime has to be baked into the language's design, or at the least you have to have designed your language in a way that makes it easy (Haskell is a good example here - because functions are pure, you don't have to worry about implicit concurrent modifications of the same data). Python, as a language, has a lot of reliance on global context and a lot of dynamic functionality. Imports happen at runtime, not compile time. You can modify the attributes of a class. You can reassign a function. And so forth. You can do nonsense like this, where a isn't even defined until the second-to-last line: $ python3 >>> def f(): ... yield 5 ... yield a ... >>> x = f() >>> next(x) 5 >>> a = 10 >>> next(x) 10 All of this needs some answer when writing concurrent / parallel code; the answer of "Don't worry about it" just gets you racy and unreliable code. The answer of "Make it impossible" changes the language (which is a fine answer, but you're better off starting from scratch like Go than adapting Python). So the remaining answer is "Make the programmer explicitly acknowledge concurrency." https://glyph.twistedmatrix.com/2014/02/unyielding.html https://glyph.twistedmatrix.com/2014/02/unyielding.html (which predates Python having proper async-await support by a few years, and references the "Tulip" project that became asyncio) makes a good argument that "Don't worry about it" doesn't work and "Make the programmer explicitly acknowledge concurrency" is fine in practice. Anecdotally, my (limited) experience writing concurrent Python code in Trio has required no learning of the intricacies of coroutines and the implementation and also relatively easy-to-follow code, but yes, with explicit references to concurrency. Also, anecdotally, since my performance problem is just "I would like to parallelize multiple HTTP requests" and not "I am an OS scheduler and need to optimize every cycle" (in which case I wouldn't be writing Python), I'm happier writing code that accounts for context switches but in turn never has to care about locking than trying to properly manage locks and lock ordering and think about types of locks and all that.
- EGreg 5y agoIf you want to unravel async loops without leaks etc. I think you may want to steal this function: https://github.com/Qbix/Platform/blob/01604218d06ed158c921ab026cb73004c2a6bf1d/platform/plugins/Q/web/js/Q.js#L2220 https://github.com/Qbix/Platform/blob/01604218d06ed158c921ab... (There are many other cool things in that js file btw)
- jwmoz 5y agoI went through the asyncio docs multiple times and could just not get it. Only after working with it and having to debug and test things did parts of it make sense. A very difficult api to work with imo.
- fregante 5y agoThis reminds me of something I’d like to see in JavaScript: Loops that accept `await` in their body without pausing the whole loop. This is already possible with: await Promise.all(arr.map(async item => { await item() }) … but it requires turning the iterable into an array first and it requires calling a function. Is anyone working on a proposal for this? async for (const item of iterable) await item()
- jimmydief 5y agoA little more verbose than your example but https://github.com/tc39/proposal-async-do-expressions https://github.com/tc39/proposal-async-do-expressions would help with this.
- fregante 5y agoThanks! I think it wouldn’t await the whole loop though for (const item of iterable) async do { await item() } This would be equivalent to Promise.all(arr.map(async item => { await item() }) Notice the lack of await before Promise.all
- eurasiantiger 5y agoThere is already this: for await (bar of foo) { } Useful if you have an array of promises you want to handle sequentially.
- fregante 5y ago`for await` is for async iterables, which is different. The loop body will still block the loop. Compare it to the all/map example I wrote.
- eurasiantiger 5y agoTrue, it is sequential and not parallel. I think this syntax with an async generator could be made to do what you seek.
- travisgriggs 5y agoI like Python. We use it as the primary language on an industrial agricultural automation device (raspberry pi form factor, running on Debian). Python’s been great for development, and the self hosted nature has made rapid in-field development/debugging awesome. My belief is that async is going to complicate things. I do not foresee myself using it. This is just not a problem point I’ve wanted to solve enough that I’ve wanted the two color problem. And I find coroutines quickly difficult to follow. I’ve been learning Elixir these last few months. When I need to solve the kinds of problems async supposedly address, I find the basic fundamental nature of Erlang/Elixir so much better suited to this.
- baybal2 5y agoJS recently got ‘async for’ too as for `await...of`
- superdisk 5y agoOh god, Python has completely lost its way. Seriously hope I never have to work with this crap. Async/await is a toxic pollution of any language's syntax and really seems like the wrong model anyway. Erlang/Elixir/Go's concurrency model is so much more intuitive and doesn't require these hideous things to be bolted onto the syntax.