| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 | 31 |
- 독서일기
- 영화일기
- 티스토리챌린지
- 플러터
- 프로그래머스
- 자바
- 영화후기
- 새벽녘 소소한 기록
- 코딩공부
- 일기
- Flutter Toy Project
- 키노
- 영화
- SOPT
- java
- MVVM-C
- swift concurrency
- 오블완
- 백준
- SWIFT
- sopt ios
- 영화기록
- 토이프로젝트
- 리뷰
- Flutter
- 영화리뷰
- sopt 35기
- 자바 스터디
- 자바공부
- IOS
- Today
- Total
새벽의 기록
[iOS] Swift Concurrency 박살내기 #2 - 구조화된 동시성 (Structured Concurrency) vs 구조화되지 않은 동시성 (Unstructured Concurrency) (Task, TaskGroup, async let) 본문
[iOS] Swift Concurrency 박살내기 #2 - 구조화된 동시성 (Structured Concurrency) vs 구조화되지 않은 동시성 (Unstructured Concurrency) (Task, TaskGroup, async let)
OneTen 2025. 10. 20. 16:59Task
https://developer.apple.com/documentation/swift/task
Task | Apple Developer Documentation
A unit of asynchronous work.
developer.apple.com
”A unit of asynchronous work.”
비동기적으로 실행되는 작업의 단위
Task는 Swift Concurrency에서 비동기 작업을 실행하는 가장 기본적인 단위이다.
async 함수가 아닌 곳(예: 버튼 클릭 이벤트)에서 async 세계로 진입하는 '다리' 역할을 한다.
구조화되지 않은 동시성 (Unstructured Concurrency)
- Task { ... } 이니셜라이저로 생성된 작업은 구조화되지 않았다. 즉, Unstructured Concurrency 인데, 이것에 관한 설명은 뒤에서 하겠다.
- 이는 Task를 생성한 부모 스코프(예: 함수)가 종료되더라도, Task는 자신의 작업이 완료되거나 취소될 때까지 독립적으로 계속 실행됨을 의미한다.
- 이러한 특성 때문에 "Fire-and-forget (실행하고 잊어버리기)" 작업에 자주 사용된다.
컨텍스트 상속 (Context Inheritance)
- Task는 생성되는 시점의 컨텍스트(Context)를 상속받는다.
- 만약 @MainActor 함수 내에서 Task { ... }를 생성하면, 그 Task의 코드도 기본적으로 @MainActor에서 실행된다.
- 또한, 부모 작업의 우선순위(QoS)를 상속받는다.
하지만 Task.detached { ... }는 부모로부터 어떤 컨텍스트도 상속받지 않는 완전히 독립된 작업을 생성한다.
Task는 async 함수가 아닌 곳에서 비동기 작업을 시작할 때 사용한다.
// SwiftUI 버튼
Button("Upload Data") {
// Task { ... }로 비동기 진입
Task {
do {
// 이 Task는 버튼 핸들러가 리턴된 후에도
// 백그라운드에서 계속 실행됨 (Unstructured)
let result = try await networkService.uploadData(mydata)
// UI 업데이트는 MainActor에서 수행
await MainActor.run {
self.statusText = "\\(result.id) 업로드 성공!"
}
} catch {
// 에러 처리
await MainActor.run {
self.statusText = "업로드 실패: \\(error.localizedDescription)"
}
}
}
}
TaskGroup
https://developer.apple.com/documentation/swift/taskgroup
TaskGroup | Apple Developer Documentation
A group that contains dynamically created child tasks.
developer.apple.com
“A group of tasks that are created dynamically”
동적으로 생성되는 작업들의 그룹.
TaskGroup은 구조화된 동시성(Structured Concurrency)을 구현하는 핵심 도구로, 여러 개의 자식 작업(child task)을 병렬로 실행하고, 그 작업들이 모두 완료될 때까지 대기하며, 결과를 수집하는 데 사용된다.
구조화된 동시성 (Structured Concurrency)
- TaskGroup은 withTaskGroup (또는 withThrowingTaskGroup)이라는 스코프(Scope) 안에서 생성된다.
- 이 스코프는 그룹에 추가된 모든 자식 작업이 완료되기 전까지 절대 리턴하지 않는다.
- 이는 작업 누수(Task Leak)를 원천적으로 방지한다.
구조화된 동시성과 구조화 되지 않은 동시성이 대체 뭐가 다른데??
이건 조금 뒤에서 다루겠다.
동적 작업 추가 (Dynamic Creation)
- async let은 컴파일 시점에 몇 개의 작업을 실행할지 정해져 있지만(정적), TaskGroup은 런타임에 동적으로(예: for-in 루프) 작업을 추가할 수 있다.
→ 즉, 이미 어떤 작업을 할 것인지 혹은 개수가 정해져 있는 상황에서는 async let을 써도 무방하다. async let 도 역시 뒤에서 다룰 예정 - group.addTask { ... }를 호출하면 자식 작업이 즉시 병렬로 실행된다.
결과 수집 (Result Collection)
- 그룹의 모든 자식 작업은 withTaskGroup(of: ...)에 명시된 동일한 타입의 결과를 반환해야 한다.
- for await result in group { ... } 구문을 사용하여 결과를 수집한다.
- 결과는 작업이 추가된 순서가 아니라, 완료되는 순서대로 도착한다.
자동 취소 전파 (Automatic Cancellation)
- 자식 작업 중 하나라도 에러를 throw하면, 그 즉시 그룹 내의 다른 모든 실행 중인 자식 작업들이 자동으로 취소된다.
- 혹은 withTaskGroup 스코프가 (예: return이나 throw로) 조기에 종료되면, 모든 자식 작업이 자동으로 취소된다.
- group.cancelAll()을 통해 수동으로 그룹 전체를 취소할 수도 있다.
이는 taskgroup의 특징이라기 보다는 구조화된 동시성의 특징인데, 이것 역시 뒤에서…
func downloadAllImages(urls: [URL]) async throws -> [UIImage] {
var images: [UIImage] = []
// 1. TaskGroup 생성
try await withThrowingTaskGroup(of: UIImage.self) { group in
// 2. URL 개수만큼 동적으로 작업 추가
for url in urls {
group.addTask {
// 이 작업들은 병렬로 실행됨
// 즉, 각각 스레드를 사용함
return try await downloadImage(from: url)
}
}
// 3. 작업이 "끝나는 순서대로" 결과를 받아서 처리
// (순서 보장 안 됨)
for try await image in group {
images.append(image)
}
// 4. 이 블록이 끝나는 시점엔 모든 group 작업이 완료됨을 보장
}
print("모든 이미지 다운로드 완료!")
return images
}
taskgroup은 Structured Concurrency이기에, 만약 downloadImage 하나라도 throw를 하면, 그룹 내 다른 모든 작업이 즉시 자동으로 취소된다.
async let
서로 다른 타입의, 미리 정해진 개수의 비동기 작업을 동시에 실행할 때 사용한다.
- async let으로 작업을 선언하는 순간, 즉시 병렬로 실행이 시작된다.
- 코드는 await를 만날 때까지 멈추지 않고 아래로 계속 진행한다.
- 나중에 await 키워드로 해당 작업의 결과를 요청할 때, 작업이 끝나지 않았으면 그제서야 **일시 중단(suspend)**된다. (Block 아님!!)
async let으로 수정한 예시
기존에는 이렇게 쓰고 있었어서 하나의 호출이 완료되면 다음 호출이 시작되고 있었다. 그래서 View에서는 순서대로 로딩이 되는 것처럼 보이고 있었고, 이는 인터넷 속도가 느린 것처럼 보이게 만들어 UX적인 부분에서 부정적인 영향을 주고 있다고 느껴졌다.
.onAppear {
Task {
await viewModel.getUserInfo()
await viewModel.getRecommendCertificationList()
await viewModel.fetchPreCertification()
await viewModel.getFavoriteCertificationList()
}
}
await으로 호출해서 순서가 보장됐던 기존의 방식에서 async let으로 비동기 함수들을 병렬 처리 하고 마지막에 await을 통해 한 번에 결과를 불러오는 방식으로 바꿨다. 이렇게 처리하니 순서대로 로딩이 되는 것처럼 보이던 뷰가 한 번에 모두 패치되는 것처럼 보여 UX적인 측면에서 긍정적인 영향을 줄 수 있었다.
.onAppear {
Task {
async let userInfo: () = viewModel.getUserInfo()
async let recommendList: () = viewModel.getRecommendCertificationList()
async let preCertifications: () = viewModel.fetchPreCertification()
async let favoriteList: () = viewModel.getFavoriteCertificationList()
_ = await (userInfo, recommendList, preCertifications, favoriteList)
}
}
구조화된 동시성 vs 구조화되지 않은 동시성
정말 간단하게 말하면 부모 작업이 어떤 이유로 인해 취소되거나 에러가 발생하면 자식 작업들도 전부 취소되는 게 구조화된 동시성이고 부모 작업과 별개로 작동하기에 부모 작업이 취소되든 에러가 발생하든 상관없이 실행되는 게 구조화 되지 않은 동시성이다.
구조화된 동시성 (Structured Concurrency):
- 정식 프로젝트 팀과 같다.
- 특징: 매니저(부모 작업)가 팀원(자식 작업)들을 관리한다.
- 생명주기: 팀원은 매니저보다 오래 일할 수 없다. (부모 작업이 끝나면 자식 작업도 끝나야 함)
- 취소: 매니저가 프로젝트를 취소하면, 모든 팀원에게 자동으로 취소 신호가 간다.
- 에러: 팀원 한 명이 **실패(Error)**하면, 즉시 매니저에게 보고되고 프로젝트 전체가 중단될 수 있다.
- 도구: async let, TaskGroup
구조화되지 않은 동시성 (Unstructured Concurrency):
- 외주 프리랜서와 같다.
- 특징: 회사(현재 스코프)가 프리랜서(새 Task)에게 일을 맡깁니다.
- 생명주기: 프리랜서는 회사의 프로젝트가 끝나도 자기 일을 계속할 수 있다. (현재 스코프를 벗어나서도 실행됨)
- 취소: 프로젝트가 취소되어도 프리랜서는 모른다. 즉, 수동으로 따로 연락해서 취소해야 한다.
- 에러: 프리랜서가 실패해도 회사는 모른다. 수동으로 결과물을 확인해야 알 수 있다.
- 도구: Task { ... }, Task.detached { ... }
이 세 경우의 사용 예시와 그 결과를 자세히 확인해보고 싶다면 아래 블로그를 추천한다.
async let vs TaskGroup vs 연속 await
Swift Concurrency는 async/await 문법을 통해 비동기 작업을 직관적으로 표현할 수 있게 해준다.하지만 비동기 작업을 병렬로 실행하는 방법은 여러 가지가 있고,이를 잘못 사용하면 성능 이점을 살리
joho.tistory.com
참고
https://joho.tistory.com/34
https://joho.tistory.com/61
https://jimmy-ios.tistory.com/47
https://yudonlee.tistory.com/43
'[iOS]' 카테고리의 다른 글
| [iOS] 결합도와 응집도에 관해 + CoordinatorView에 대한 스스로의 판단 한 스푼 (1) | 2025.11.26 |
|---|---|
| [iOS] Swift Concurrency 박살내기 #3 - Actor (0) | 2025.11.20 |
| [iOS] Swift Concurrency 박살내기 #1 - iOS에서의 동시성과 비동기 프로그래밍 방법 (0) | 2025.10.18 |
| [iOS] Swift Concurrency 박살내기 #0 - 동시성과 비동기 (0) | 2025.10.15 |
| [iOS] SwiftUI에서 MVVM-C 패턴 톺아보기 (0) | 2025.10.02 |
