김경록의 앱 개발 여정

[Swift Concurrency] 구조적 동시성의 취소(feat: 명시,암시적 취소전파) 본문

TIL

[Swift Concurrency] 구조적 동시성의 취소(feat: 명시,암시적 취소전파)

Kim Roks 2025. 4. 18. 16:55

✅ 구조적 동시성과 작업 취소

구조적 동시성이란?

  • 부모 Task가 종료되기 전까지, 그 아래에서 생성된 자식 Task들도 모두 종료되어야 하는 규칙
  • Swift의 async let, withTaskGroup 이 대표적인 구조적 동시성 도구
  • 취소는 위에서 아래로 전파됨 (부모 → 자식)

즉, 부모가 취소되면, 구조적으로 연결된 자식들도 자동으로 취소됨
구조적 동시성에 관한 이전 글 ) https://roks-apps.tistory.com/76

 


✅ async let의 작업 취소

특징

  • async let 은 선언된 순간 비동기 작업을 시작하고, 자동으로 취소 전파 대상이 됨
  • await 하기 전에 부모 Task가 취소되면, async let 작업도 취소됨
  • 특별히 .cancel() 하지 않아도 자동으로 정리(cleanup)

예시

func loadData() async {
    async let user = fetchUser()
    async let posts = fetchPosts()

    do {
        let result = try await (user, posts)
        print("결과: \(result)")
    } catch is CancellationError {
        print("작업이 취소되었습니다.")
    }
}
  • 위에서 loadData() 가 취소되면 fetchUser() 와 fetchPosts() 도 같이 취소됨
  • try await 하는 시점에서 취소되었으면, CancellationError 가 던져짐

✅ TaskGroup의 작업 취소

특징

  • withTaskGroup 또는 withThrowingTaskGroup 내에서 만든 Task들은 부모 TaskGroup에 소속
  • 부모가 cancelAll() 호출하면, 그 시점 이후 추가된/미완료된 작업들이 취소됨
  • 작업 내부에서는 Task.isCancelled 이나 Task.checkCancellation()로 반응하도록 구현해야 함

예시

  • group.cancelAll()은 이미 끝난 작업에는 영향 없음
  • 각 작업 내부에서 직접적으로 취소에 반응하도록 처리해야 함 (Task.isCancelled 등 사용)
func processTasks() async {
    await withTaskGroup(of: Void.self) {
        group in for i in 0..<5 {
            group.addTask {
                try? await Task.sleep(nanoseconds: UInt64(i) * 500_000_000)
                if Task.isCancelled {
                    print("Task \(i) 취소됨")
                    return
                }
                print("Task \(i) 완료")
            }
        } // 1초 후 취소 try? await Task.sleep(nanoseconds: 1_000_000_000) group.cancelAll() // -> 아직 끝나지 않은 Task 들에 취소 요청 }
    }
}

 


✅ 비교 정리

항목async letTaskGroup
구조적 동시성
취소 전파 부모 취소 시 자동 전파 cancelAll() 호출로 수동 전파
자식 작업 취소 처리 자동 수동 (isCancelled, checkCancellation)
예외 전파 가능 (자동 throw) 가능 (withThrowingTaskGroup 사용 시)
결과 수집 튜플 등으로 간단하게 루프나 배열로 직접 처리

🧠 요약

  • async let: 간결, 취소 자동 처리, 에러 자동 전파
  • TaskGroup: 유연, 작업 수동 취소 가능, 복잡한 컨트롤에 적합
  • 둘 다 부모 취소 시 자식도 함께 취소됨 → 결국 이게 구조적 동시성의 핵심

✅ 명시적 취소 전파 (Explicit Cancellation Propagation)

  • 개발자가 직접 코드로 취소를 요청하거나
    직접 자식 작업에게 신호를 보내는 방식

예시

1. Task.cancel(), group.cancelAll() 같은 메서드 호출

let task = Task { // 작업 내용 } task.cancel() // 👈 명시적으로 취소 전파

2. TaskGroup 내에서 수동으로 취소 요청

swift
복사편집
await withTaskGroup(of: Void.self) { group in
    group.addTask { await someWork() }
    group.cancelAll() // 👈 명시적 취소 요청
}

 

3. 내부 로직에서 명시적으로 Task.isCancelled 체크

if Task.isCancelled {
    // 👈 명시적으로 체크하고 정리
    return
}

✅ 암시적 취소 전파 (Implicit Cancellation Propagation)

  • Swift의 구조적 동시성에 의해 자동으로 일어나는 취소 전파
  • 부모 Task가 취소되면, 구조 내 자식 Task들도 자동으로 취소 요청을 받음

예시

1. async let 이 포함된 부모 Task가 취소되면

func parentTask() async {
    async let user = fetchUser()
    async let posts = fetchPosts()
    
    // 이 Task 자체가 취소되면 user, posts 도 암시적으로 취소됨
}

2. withTaskGroup 안의 작업들도 부모 Task가 취소되면 자동 취소

func parent() async {
    await withTaskGroup(of: Void.self) { group in
        group.addTask {
            await longRunningWork()
        }
        // 이 함수 전체가 취소되면, group의 자식 작업도 암시적으로 취소됨
    }
}

3. try await 로 취소에 대응하는 시스템 API 사용 시

try await URLSession.shared.data(from: url)
// Task가 취소되면 암시적으로 이 API도 중단됨

🧠 비교 정리

 

구분 명시적 취소 전파 암시적 취소 전파
정의 개발자가 직접 .cancel(), cancelAll() 등을 호출 구조적 동시성 또는 시스템 동작에 의해 자동 발생
예시 task.cancel(), group.cancelAll() 부모 Task 취소 → 자식 Task 자동 취소
Task 내부 대응 보통 Task.isCancelled, checkCancellation() 사용 대부분 CancellationError throw 혹은 시스템 API 취소
코드 관여도 높음 (수동 처리 필요) 낮음 (자동 전파)
유용한 상황 조건부 취소, 복잡한 제어 필요 시 단순 구조, 자동 관리 시