Let's say I have the following functions.
func first() async {
print("first")
}
func second() {
print("second")
}
func main() {
Task {
await first()
}
second()
}
main()
Even though marking first
function as async does not make any sense as there is no async work it does, but still it is possible...
I was expecting that even though the first function is being awaited, it will be called asynchronously.
But actually the output is
first
second
How would I call the fist function asynchronously mimicking the GCD's variant of:
DispatchQueue.current.async { first() }
second()
CodePudding user response:
The second
task is actually not waiting for the first
task, which runs on a separate thread, to finish. Actually do something time consuming in the first
task and you will see the second
task actually isn’t waiting at all.
Using Task { ... }
is more to DispatchQueue.global().async { first() }
. The first
and second
tasks are running on separate threads, so you simply have a race and you have no assurances as which order the print
statements run. (In my tests, it runs second
before first
most of the time, but it can still occasionally run first
before second
.)
So, the question is, do you really care which order these two tasks start? If so, you can eliminate the race by (obviously) putting the Task { await first() }
after the call to second
. Or do you simply want to ensure that second
won’t wait for first
to finish? In that case, this already is the behavior and no change to your code is required.
You asked:
What if
await first()
needs to be run on the same queue assecond()
but asynchronously, is that possible with theTask
api, or GCD would be needed to achieve that?
You can do this with modern concurrency API, by marking the routine to update the UI with @MainActor
. But note, do not use this qualifier with time-consuming task, itself (because you do not want to block the main thread), but rather decouple the time-consuming operation from the UI update, and just mark the latter as @MainActor
.
E.g., here is an example that manually calculates π asynchronously, and updates the UI when it is done:
func startCalculation() {
Task {
let pi = await calculatePi()
updateWithResults(pi)
}
updateThatCalculationIsUnderway() // this really should go before the Task to eliminate any races, but just to illustrate that this second routine really does not wait
}
// deliberately inefficient calculation of pi
func calculatePi() async -> Double {
var value: Double = 0
var denominator: Double = 1
var sign: Double = 1
var increment: Double = 0
repeat {
increment = 4 / denominator
value = sign * 4 / denominator
denominator = 2
sign *= -1
} while increment > 0.000000001
return value
}
func updateThatCalculationIsUnderway() {
statusLabel.text = "Calculating π"
}
@MainActor
func updateWithResults(_ value: Double) {
statusLabel.text = "Done"
resultLabel.text = formatter.string(for: value)
}