I'm trying to make sure I understand the behavior of await
. Suppose we have the following functions:
func do() async {
//code
}
func stuff() async {
//code
}
The following statements will run do
and stuff
sequentially:
await do()
await stuff()
But the following statement will run do
and stuff
in parallel correct?
await (do(), stuff())
I'm not sure how to check in Xcode if my code runs in parallel or in sequence.
Short answer:
If you want concurrent execution, either use async let
pattern or a task group.
Long answer:
You said:
But the following statement will run do and stuff in parallel correct?
await (do(), stuff())
No, they will not.
This is best illustrated empirically by:
Consider this code, using the tuple approach:
import os.log
actor Experiment {
private let logger = OSSignposter(subsystem: Bundle.main.bundleIdentifier!, category: .pointsOfInterest)
func example() async {
let values = await (doSomething(), doSomethingElse())
print(values)
}
func doSomething() async -> Int {
spin(#function)
return 1
}
func doSomethingElse() async -> Int {
spin(#function)
return 42
}
func spin(_ name: StaticString) {
let state = logger.beginInterval(name, id: logger.makeSignpostID(), "begin")
let start = ContinuousClock.now
while start.duration(to: .now) < .seconds(1) { } // spin for one second
logger.endInterval(name, state, "end")
}
}
That results in a graph that shows that it is not happening concurrently:
Whereas:
func example() async {
async let foo = doSomething()
async let bar = doSomethingElse()
let values = await (foo, bar)
print(values)
}
That does result in concurrent execution:
Now, in the above examples, I changed the functions so that they returned values (as that is really the only context where using tuples makes any practical sense).
But if they did not return values and you wanted them to run in parallel, you might use a task group:
func experiment() async {
await withDiscardingTaskGroup { group in
group.addTask { await self.doSomething() }
group.addTask { await self.doSomethingElse() }
}
}
func doSomething() async {
spin(#function)
}
func doSomethingElse() async {
spin(#function)
}
That also results in the same graph where they run in parallel.
You can also just create Task
instances and then await
them:
func experiment() async {
let task1 = Task { await doSomething() }
let task2 = Task { await doSomethingElse() }
await withTaskCancellationHandler {
_ = await task1.value
_ = await task2.value
} onCancel: {
task1.cancel()
task2.cancel()
}
}
As you can see, if you use unstructured concurrency, you have to handle cancelation manually.
However, task groups offer greater flexibility when the number of created tasks may not be known at compile-time.
No, they are not executed in parallel.
Firstly,
await (do(), stuff())
is shorthand syntax for
(await do(), await stuff())
which is evaluated from left to right, just like any other lines of code.
Now, any await
call suspends the execution of the caller, if the callee suspends. This means the execution will continue with await stuff()
only after await do()
finishes.
await
is a suspension point (actually a possible suspension point), this means the execution won't continue until that await
complete (in either an async or a sync manner, it doesn't matter).
I'm not sure how to check in Xcode if my code runs in parallel or in sequence
You don't need to, unless you don't trust the compiler. The only ways to spawn multiple await
from the same async
context (task) is via async-let
and TaskGroup
.
But if you really want to check, for learning purposes, others have provided some interesting approaches. I'm just gonna leave mine, which is similar to another already provided one:
func delay(for interval: TimeInterval = 2.0, marker: StaticString = #function) async {
print("\(marker) begin")
try? await Task.sleep(nanoseconds: UInt64(interval * 1_000_000))
print("\(marker) end")
}
func `do`() async {
await delay()
}
func stuff() async {
await delay()
}
Task {
let result = await (`do`(), stuff())
}
The above code results in the following output:
do() begin
do() end
stuff() begin
stuff() end
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With