Indeed it would be useful to have some name for the task created by async{} in the proposal I just called it an “async task” vs a “detached task”. This is pretty true, it creates a task (internally known as AsyncTask), which is neither a child task, nor a detached task, so calling it by the plain old async task sounds good to me.
It cannot be a child task–by construction–because we would not be able to allocate it using the task allocator nor can we force it to end before the task from which it was created ends — because we’re potentially in a synchronous function and thus cannot await on it.
Ah! That does clarify. Seeing your nice updated text in the context of the larger proposal is quite helpful.
The name is not perfectly satisfying. (Aren’t all tasks asynchronous?)
Can you elaborate on that? Is it simply because there is no globally available notion of “current task?” Or is it something more subtle?
It seems in my naiveté that this isn’t strictly true: could a synchronous function not receive a taskGroup parameter, use taskGroup.spawn to create the child-that-is-truly-a-child task, but then exit immediately without awaiting?
I think this is a feature for the awkward situation where you would normally create a child task, but for some technical reason your caller needs you to be non-async, and your caller doesn’t actually need to wait for the task to finish. For example, an @IBAction can’t be async (AFAIK), but AppKit/UIKit doesn’t actually want to wait for the whole operation to finish—it just wants you to start it and return so it can process the next event.
Maybe something like withoutActuallyAwaiting(_:) would be a good name for this function.
no parent: since we're in a synchronous context, there may or may not be a task around to become a child of; so we can't just say this is a child task
lifetime: even if there was a parent to attach to, we'd by design by violting structured concurrency guarantees here. the purpose of this function is to not wait which violates parent/child task guarantees. The async created task can out-live the task from which it was created.
The lifetime issue also manifests in making some optimizations impossible, but that's secondary reasons.
We can't do that, as it would violate structured concurrency guarantees. The group must wait on all spawned children before it exits. This is why the group forms a scope: withTaskGroup { group ... } and it is not legal to pass around or escape the group.
Hah yeah that's a pretty lengthy but somewhat precise name for it... We found though that the use of this function is frequent so we would argue for a short nice name for it.
Though it also can return a Task.Handle which one could await on (in the revised structured concurrency proposal linked above).
I'm very happy to see this. Looks like a piece of the puzzle that was missing.
Just some notes:
1.
async {
g() // okay to call g synchronously, even though it's @Sendable
}
Looks a bit frustrating. My own mental model is that async {} gives me an ability to run some synchronous task asynchronously, like Dispatch.queue.async {}.
But in fact it behaves completely different. Though I see the word "async", in fact it behaves as "sync".
May be a compiler error or warning should appear, if we call sync function in async block?
The word async here is treated as a verb, but in other places it is adjective.
We already have do {} block, so the idea here is to name it as "asyncDo". Then we will have:
asyncDo { g() } // compiler warning
asyncDo { await g() } // ok
asyncDetached { await g() } // ok