4 ms·
I don't think "task cancellation" is quite the major difference you think. If you model it as thread A wants to cancel thread B, then while threading means that
by jerf 6y ago
I don't think "task cancellation" is quite the major difference you think. If you model it as thread A wants to cancel thread B, then while threading means that A runs and cancels B, but B may need some time to catch up, the async world has the problem of thread A running at all to cancel B, if B is having a problem that requires cancellation. It's "obvious" and "safe" until it doesn't happen at all.
This is a pervasive problem with the async/await model. As it scales up the probability of something failing to yield when it should and blocking everything else continually goes up as the code size increases, and then the whole model, correctness, practicality, and all, just goes out the window. While it is small for small programs, and it the scaling factor often isn't that large, it is still a thing that happens. Entire OSes used to work that way, with the OS and user processes cooperatively yielding, and what killed that model is this problem.
Also, I'm writing a lot of code lately where I can peg multiple cores at a time, with a relatively (if not maximally efficient) language like Go; having to also write it as a whole bunch of OS processes separately running because my runtime can only run on one core at a time is a non-starter, and "async/await" basically turns into a threading system if you try to run it on multiple cores in one system anyhow.
These two fatal-for-me flaws mean it's a non-starter for a lot of the work I'm doing anyhow, regardless of any other putative advantages.
(As I mentioned, I'm using Go, but if you want to see a runtime that really has the asynchronous exceptions thing figured out, go look at Erlang. Having a thread run off into never-never-land and eating a full CPU isn't fun, but being able to log in to your running system, figure out which it is using a REPL, kill just that thread, and reload its code before restarting it to fix the problem, all without taking down the rest of your system is not an experience most of you have had. But it can be done!)
- jnwatson 6y agoAsync, and cooperative multitasking in general, requires all members participate in the contract: you shall not block and you shall not go too long until yielding. Once a piece of code violates that, all bets are off. Python has explicit debugging mechanisms to help a developer detect the latter. Cancel safety is less about killing an out-of-control task, and more about making sure the state after cancelling a task is consistent.
- jayd16 6y ago>you shall not block and you shall not go too long until yielding Eh... you as the caller have control on how a task is run. If you want it on a different thread and non-blocking, or simply deferred, you can. You have a lot of control. With an implicit system you have much less control. Loom seems to solve this by letting you explicitly schedule tasks anyhow?
- didibus 6y ago> you shall not block and you shall not go too long until yielding Arn't those effectively the same? I think the only contract is "you shall not go too long until yielding". Or if you're designing for performance, not for responsiveness, it might be: "you shall not hold unto CPU and IO resources that you're not using".
- dnautics 6y ago> but if you want to see a runtime that really has the asynchronous exceptions thing figured out, go look at Erlang. The erlang VM does indeed have async exceptions, and resource management figured out. Usually you can just kill an erlang process and you don't have to clean up after it's open sockets, file descriptors, etc. It's also possible to hook the C FFI system to take full advantage of that: https://youtu.be/l848TOmI6LI https://youtu.be/l848TOmI6LI (Disclaimer: self promotion)
- earthboundkid 6y agoCancellation is a little tricky. If things can cancel at any point, then it's impossible to write safe/correct code. Async has the advantage that the await calls are natural sync points, so it's (probably) safe to cancel there. But historical experience has also shown that people will forget to yield when they should, and it will lead to cooperation problems. I think the best approach has to look something like Go's, but perhaps a bit more structured (dynamic scoping[1] might help perhaps with task nurseries[2]). Unless you're writing extremely low level code, you want your language runtime to intercept all syscalls and figure out the async story for you. The language should handle making sure that the M:N mapping works out, no one opens a socket the wrong way etc. Then for you as the program writer, your responsibility is just setting explicit cancellation points as part of the general error handling approach. It's still not perfect, but I think that would be the next evolution from what exists today. [1] https://blog.merovius.de/2017/08/14/why-context-value-matters-and-how-to-improve-it.html https://blog.merovius.de/2017/08/14/why-context-value-matter... [2] https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/ https://vorpus.org/blog/notes-on-structured-concurrency-or-g...
- jayd16 6y agoIf Loom isn't adding cancellation tokens to the core library then its going to be a major difference. That said, the Java way was already for runnables to cancel themselves with some kind of volatile cancel bool so I expect that's all we'll get. From the article: >Clearly the intent is also to cancel the child thread. And this is exactly what happens. I find this to be a bold choice. In C# you can detach children and such. Should be interesting to see if this gets added later.