Hi Subrahmanyam,

There are a multitude of possible Structured Concurrency APIs, all with 
different trade-offs in discoverability, ease-of-use, difficulty-of-misuse, 
performance, composability, maintainability, reviewability, and more. (Alan and 
I have prototyped dozens of different ones.)

If an API with the shape you outline above would suit your use-case(s), it does 
look tractable for you to create that on top of Structured Concurrency APIs 
from the JEP.

Cheers,
√


Viktor Klang
Software Architect, Java Platform Group
Oracle
________________________________
From: loom-dev <[email protected]> on behalf of Subrahmanyam V 
<[email protected]>
Sent: Thursday, 21 August 2025 13:07
To: [email protected] <[email protected]>
Subject: Style difference between StructuredTaskScope and ScopedValue APIs

Hello all, I am new here but just wanted to ask a question & share a thought on 
StructuredTaskScope API.

What's the reason for the asymmetry between ScopedValue and StructuredTaskScope 
APIs? Symmetry would suggest something like 
`StructuredTaskPolicy.create(Supplier<Joiner>).call(Function<StructuredTaskScope,
 T>)`.

Note
(1) StructuredTask_Policy_. The _call_ method on a Policy instance creates a 
StructuredTaskScope, passes it to the Consumer/Function and closes it before 
returning. Scoped Value propagation should behave like in the current API.
(2) Supplier<Joiner> in constructor because Joiner is stateful.

On the surface, there are a few advantages to the symmetric API
1. [Immutable, well-defined Policies] StructuredTaskPolicy instances can be 
immutable and shared across the application and its many threads. I can imagine 
applications that use just 2 different policies {all-succeed, any-succeed} 
across tens or hundreds of call sites
2. [Expressions] This API produces expressions instead of imperative constructs 
and thus composes better. For example, I can do: `return awaitUniterruptibly(() 
-> ALL_SUCCEED_POLICY.call(scope -> {...}));`.
3. [Managed Scope lifecycle] Let the API manage scope.close() to free resources 
instead of relying on the user. Hide lifecycle methods like open() and close() 
and expose only fork() and join() on StructuredTaskScope - the only ones that 
the user really cares about.
4. [Brevity that leads to Clarity] Nesting is feasible given brevity and 
expressions (#2). For example, a single readable expression can express that 
two subtasks should each succeed where the first task is itself a 
StructuredTask made up of three subtasks any of which succeeds,
5. [Easier evolution] If StructuredTaskScope.Config API evolves (this seems an 
active area of change), only Policy classes need to be evaluated by users for 
any potential migration instead of every call-site. A handful of immutable 
Policy classes is easier to manage than 10s or 100s of call-sites.

But, on reflection, an application can get all of the above benefits if it 
sticks exclusively to the _race()_ or _runConcurrently()_ constructs from the 
JEP. That is, instead of _Policy Classes_, it uses _Policy Methods_ like 
race(). Which begs the question - should the structured concurrency API also 
expose Policy Methods as a first class API, one Policy Method per Joiner?

Subrahmanyam


Reply via email to