Section 3/131 menit
3. Structured Concurrency
3. Structured Concurrency
Teori: Task Tree dan Structured Concurrency
Swift Concurrency menggunakan model structured concurrency — setiap Task memiliki parent, dan lifetime-nya terikat pada scope yang membuatnya. Ini mencegah "fire and forget" yang tidak terdeteksi.
swift
Task (root)
├── async let userTask
├── async let postsTask
└── withTaskGroup
├── subtask A
├── subtask B
└── subtask C
Jika parent di-cancel atau selesai, semua child otomatis di-cancel.
Task {} vs Task.detached {}
swift
class ProfileViewController: UIViewController {
// Task {} — mewarisi actor isolation dari konteks pemanggil
// Di sini mewarisi @MainActor dari ViewController
func loadProfile() {
Task {
// Berjalan di @MainActor context (inherited)
let user = try await service.fetchUser(id: "1")
// Langsung update UI, tidak perlu dispatch ke main
self.nameLabel.text = user.name
}
}
// Task.detached {} — TIDAK mewarisi isolation, berjalan di background
// Gunakan hanya jika perlu explicitly lepas dari actor context
func processInBackground() {
Task.detached(priority: .background) {
let result = await heavyComputation()
// Harus kembali ke MainActor secara manual untuk update UI
await MainActor.run {
self.resultLabel.text = result
}
}
}
}
Rekomendasi: Gunakan
Task {}(bukanTask.detached) sebagai default.Task.detachedhanya untuk kasus yang benar-benar perlu lepas dari isolation parent.
async let — Concurrent Binding
async let menjalankan beberapa operasi secara paralel tanpa TaskGroup:
swift
func loadDashboard() async throws -> Dashboard {
// Ketiga fetch ini berjalan BERSAMAAN, bukan berurutan
async let user = service.fetchUser(id: currentUserId)
async let posts = service.fetchPosts(userId: currentUserId)
async let config = service.fetchAppConfig()
// await semua sekaligus — total waktu = waktu terlama dari ketiganya
let (u, p, c) = try await (user, posts, config)
return Dashboard(user: u, posts: p, config: c)
}
// Bandingkan dengan sequential — LEBIH LAMBAT
func loadDashboardSlow() async throws -> Dashboard {
let user = try await service.fetchUser(id: currentUserId) // tunggu selesai
let posts = try await service.fetchPosts(userId: currentUserId) // baru mulai
let config = try await service.fetchAppConfig() // baru mulai
return Dashboard(user: user, posts: posts, config: config)
}
withTaskGroup — Concurrency Dinamis
Ketika jumlah task tidak diketahui saat compile time:
swift
func fetchAllUsers(ids: [String]) async throws -> [User] {
try await withThrowingTaskGroup(of: User.self) { group in
// Tambahkan task secara dinamis
for id in ids {
group.addTask {
try await service.fetchUser(id: id)
}
}
// Kumpulkan semua hasil
var users: [User] = []
for try await user in group {
users.append(user)
}
return users
}
}
Cancellation
swift
func longRunningOperation() async throws -> [Result] {
var results: [Result] = []
for item in largeDataset {
// Cek cancellation di setiap iterasi
try Task.checkCancellation() // throws CancellationError jika cancelled
// Atau cek secara manual
if Task.isCancelled {
return results // return partial result
}
let result = try await processItem(item)
results.append(result)
}
return results
}
// Simpan referensi Task untuk bisa cancel nanti
class SearchViewController: UIViewController {
private var searchTask: Task<Void, Never>?
func search(query: String) {
// Cancel search sebelumnya jika masih berjalan
searchTask?.cancel()
searchTask = Task {
do {
let results = try await searchService.search(query: query)
self.displayResults(results)
} catch is CancellationError {
// Task di-cancel, abaikan — tidak perlu show error ke user
return
} catch {
self.showError(error)
}
}
}
}