class ViewModel: ObservableObject { }
struct ContentView: View {
@StateObject private var model = ViewModel()
var body: some View {
Button("Authenticate", action: doWork)
}
func doWork() {
Task.detached {
for i in 1...10_000 {
print("In Task 1: \(i)")
}
}
Task.detached {
for i in 1...10_000 {
print("In Task 2: \(i)")
}
}
}
}
This is the code described in https://www.hackingwithswift.com/quick-start/concurrency/whats-the-difference-between-a-task-and-a-detached-task.
Since the Tasks in doWork are detached, I expect them to be executed at the same time. The above article also says so.
However, when I run this, Task2 is executed after Task1.
Am I wrong?
Multiple detached tasks do run concurrently. Consider this example, where I perform a computationally intensive operation 20 times, each in its own task:
import SwiftUI
import os.log
private let log = OSLog(subsystem: "Detached tasks", category: .pointsOfInterest)
struct ContentView: View {
var body: some View {
Button("Do work", action: doWork)
}
func doWork() {
os_signpost(.event, log: log, name: #function)
for i in 0 ..< 20 {
Task.detached {
let id = OSSignpostID(log: log)
os_signpost(.begin, log: log, name: #function, signpostID: id, "start %d", i)
let value = calculatePi(decimalPlaces: 9)
print(value)
os_signpost(.end, log: log, name: #function, signpostID: id, "done")
}
}
}
// deliberately inefficient calculation of pi using Leibniz series
func calculatePi(decimalPlaces: Int = 9) -> Double {
let threshold = pow(0.1, Double(decimalPlaces))
var isPositive = true
var denominator: Double = 1
var value: Double = 0
var increment: Double
repeat {
increment = 4 / denominator
if isPositive {
value += increment
} else {
value -= increment
}
isPositive.toggle()
denominator += 2
} while increment >= threshold
return value
}
}
We can profile that task, using the “Points of Interest” tool. On the simulator that yields:
Note, that is artificially constraining the cooperative thread pool to two tasks at a time.
On an iPhone 12 Pro Max:
That runs six at a time.
And on an Intel 2018 MacBook Pro:
So, bottom line, it does run concurrently, constrained based upon the nature of the hardware on which you run it (with the exception of the simulator, which artificially constrains it even further).
FWIW, the 10,000 print
statements is not a representative example because:
Too many synchronizations: The print
statements are synchronized across threads which can skew the results. To test concurrency, you ideally want as few synchronizations taking place as possible.
Not enough work on each thread: A for
loop of only 10,000 iterations is not enough work to properly manifest concurrency. It is quite easy that the first task could finish before the second even starts. And even if it does start interleaving at some point, you might see a couple thousand on one task, then a couple thousand on the next, etc. Do not expect a rapid interleaving of the print
statements.
For these reasons, I replaced that for
loop with a calculation of pi to 9 decimal places. The idea is to have some calculation/process that is sufficiently intensive to warrant concurrency and manifest the behavior we are looking for.
Perhaps needless to say, if we experience serial behavior if we:
Task { ... }
rather than detached task.(Note, the horizontal time scale as been compressed to fit the twenty tasks on screen at the same time.)