Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

> as long as you only use async/await...

Well, what if you don't?

That's the point.

The proposed construct does not require that: I'm not saying its better or worse; I'm just pointing out that it's different.

Obviously if you choose to avoid branching code in your functions, they won't branch; what's novel here is that the closing scope (ie. in this case, the collapse of the python with block) triggers a collection of all the ambient tasks started in that context.

I'm sure you could implement something similar in javascript, but it is not the same as Promise.all.



>Well, what if you don't?

This is kind of a silly question. Its always possible to subvert a safe construct system if you try hard enough. You can write unsafe blocks in rust. You can pass in a callback to a nursery, as is described in the article.

>The proposed construct does not require that

Well, kind of. The article actually explicitly states that

>Here's a simpler primitive that would also satisfy our flow control diagram above. It takes a list of thunks, and runs them all concurrently:

which is equivalent to promise.all, satisfies the invariant nurseries do, except in very specific circumstances (unbounded while loop-y constructs). And you just simply can't use promise.all in that situation.


Oh come on.

I don't agree that the proposed idea is fundamentally new and amazing; but I think it is novel, and there may be some value in being able to semantically bind tasks to execution points, specifically when the tasks are spawned in naive (or uncontrolled, eg. library) code, and have not explicitly opted in to the scheme.

Does this particular implementation do that perfectly? No, probably not.

...but I think the idea of it has some merit.

There's more value here than just waiting for a series of deferred tasks that you have explicitly created, and explicitly opted into a specific concurrency workflow with.

Note specifically these parts of the proposal:

> we declare that a parent task cannot start any child tasks unless it first creates a place for the children to live

^-- This is not satisfied by your method, at all.

> But the problem with this is that you have to know up front the complete list of tasks you're going to run, which isn't always true.

Literally the next sentence after the one you quoted.

> What if you really do need to write a function that spawns a background task, where the background task outlives the function itself? Easy: pass the function a nursery object. There's no rule that only the code directly inside the

Notice how you can avoid the 'setTimeout(() => { .. })` in nested code issue by explicitly passing in a context that has its own scope.

...

There's a lot of stuff in here that is actually quite thoughtful.

You don't have to dig through it, finding every little nitpick you can to call it out on; just think about the idea being proposed.

It's certainly not 'just promises'; if you think it is, then... I don't know what to say. You're wrong. :P


>and have not explicitly opted in to the scheme.

I think you're overestimating nurseries here. They still don't handle the case you described of a deeply nested setTimeout:

    import asyncio
    import trio
    import threading

    async def my_tricky_function():
        threading.Timer(5, lambda: print('first')).start()
        await trio.sleep(.5)

    async def a_function():
        async with trio.open_nursery() as n:
            n.start_soon(my_tricky_function)
        print("finished")

    trio.run(a_function)
(requires python 3.6, I'm using threading.timer in place of setTimeout, but they're the same construct). There's a great talk by david beazly about how threads and asyncio don't play nicely, except when they do.

Note that you could imagine that this timer is created in an awaited function or a supposedly synchronous child function.

If the nursery did what you think it did, which is to wait until everything async within the block completes, you'd see this print first, then print finished. It doesn't though, finished is printed first. The reason is that you need to opt in to the trio constructs by using promises/futures/coroutines. Using threaded callback based things give you the same problems with trio as they do in other async/parallel constructs.

If you're a good citizen and opt in to the safety guarantees trio provides, it can keep things clean for you, yes. But you do need to explicitly opt into the scheme by only using code that you know is promise/future/coroutine based. Threads are a doozy.

But the same thing is pretty much true with async/await promises/coroutines in js or python. If you require that you only use promises (or you tightly wrap all of your threads/callbacks in promises and then use that), you can get the safety of nurseries in most of the situations (I noted there were some exceptions before!), but with just the async/await syntax, no need for the extra async context manager.

I agree that trio is cool. I've used it before. But you're ascribing to it and this construct magical powers that they cannot and do not have.


> they cannot and do not have.

sigh~

In this implementation, or perhaps, in python at all...

Does that make it completely valueless?

I would venture to suggest that maybe there's a big wide world of languages that actually support controlling how threads are spawned, where it might not be.

Maybe its worth considering.


Or perhaps at all.

I agree that it's worth considering other ways of handling concurrency. But we should do so by staying within the realm of reality.

Your understanding of nurseries, conceptually, does not match their capabilities. It's not about any language or implementation, it's that what you think they can do isn't possible. It violates the halting problem.

You can't statically infer whether or not a function will have async side effects without opting into some scheme that describes those effects. If you do that, nurseries provide some guarantees. But those same guarantees are provided by just using async/await, which tracks explicitly which functions are async and which are not.

Nurseries do potentially provide some advantages when dealing with tasks that you want to outlive their scope, which async/await doesn't handle well, and when dealing with an unbounded number of async calls (maybe, I think an `async for` construct handles it too).

But otherwise, most of the advantages you seem to think nurseries provide aren't. And not just by this implementation, but by any implementation. They are provably not providable by any implementation that isn't equivalent to marking your async functions as async.


You are simply not correct.

If you can control how threads spawn, like C# thread contexts (for example the MVC context forces async resumes on the original thread rather than an arbitrary one), this is entirely plausible without an 'opt in' model.


>If you can control how threads spawn

This is an opt in model.

In general, it is not possible to know if an arbitrary thread will halt. That's the halting problem. You can restrict yourself and make sure that all threads 'return' on completion (promises, futures), or do various other things. But in an unrestricted system, it is provably impossible to know whether or not an arbitrary thread will halt.


The issue is waiting for threads at runtime, at fixed boundary points, not statically determining if they halt or not.

The halting problem is irrelevant to this discussion.

Well.. whatever. You can provably determine whatever irrelevant point you want I guess.

If a naively spawned task or routine can be artificially restricted at a boundary point, that's enough for me.

You could without question implement this by binding child tasks to the threadlocal context on spawn and wait all on a dispose block in c#; certainly if you explicitly went out of your way to swap to a different sync context it would break but so what?

You can always unsafe your way into a hole in any language.


>If a naively spawned task or routine can be artificially restricted at a boundary point, that's enough for me.

Ok, and you have three options to do this:

1. Block 2. Opt into some system that allows you to defer blocking until later 3. Know when the callback will finish

1 isn't async, 2 is an opt in system, and 3 has a prerequisite of the halting problem.

That's it. You can restrict yourself to not using primitives like `Thread`, and instead only use futures or async/await style things, and that's a valid solution. Hell, your language could not expose a Thread primitive at all. But its solution #2. You cannot just take a look at arbitrary code and pick a point or points where thread execution is restrained.

If you can, it's because the language you're using prevents you from doing certain things. (for example, opting in to "all threads must be promises, and all promises must be awaited)




Consider applying for YC's Fall 2026 batch! Applications are open till July 27.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: