TIL
[Swift Concurrency] Task의 취소
Kim Roks
2025. 4. 18. 15:12
작업의 즉시 멈춤이 아닌, ‘취소를 전파’하는 개념
- Swift의 Task는 취소를 요청받더라도, 즉시 중단되는 것이 아니다.
- 대신, “취소되었다는 신호” 를 내부적으로 가지고 있고, 작업 내에서 그 신호를 직접 확인하고 적절히 대응해야 함.
- 이걸 "cooperative cancellation" 이라고도 함. 작업이 스스로 확인하고 정리하는 방식.(협동적 취소)
부모-자식 관계와 취소 전파
- Swift의 structured concurrency에서는 부모 Task가 있고, 그 안에 자식 Task가 만들어질 수 있어.
- 부모 Task가 취소되면, 자식 Task에게도 취소가 전파됨.
- 하지만! 자식 작업이 완전히 끝날 때까지 (심지어 에러가 발생했더라도), 부모 작업은 기다림.
- 즉, 취소되더라도 자식 작업은 graceful하게 끝나야 하며, 그동안 부모는 대기 상태.
취소 상태 확인: Task.isCancelled
- 작업 내부에서 현재 취소 상태인지 확인하고 싶을 때 사용
func someTask() async {
if Task.isCancelled {
print("취소 요청이 들어왔습니다.")
return
}
// 작업 수행
}
- 반복문이나 긴 작업 처리 중간중간에 Task.isCancelled를 확인하는 것이 일반적인 패턴.
try Task.checkCancellation()
- 이 함수는 Task.isCancelled를 체크하고, 취소되었다면 에러를 던짐.
- 에러 타입은 CancellationError
- Task가 취소되었을 때는 일반적인 에러 처리 흐름을 따름
- try 구문에서 Task.checkCancellation() 호출 시 CancellationError가 throw됨 → 이를 do-catch로 처리 가능
항목 설명
취소 방식 | 즉시 멈추는 게 아닌, 신호를 전달 |
부모-자식 구조 | 부모 취소 → 자식에게 전파되지만, 자식이 끝날 때까지 기다림 |
상태 확인 | Task.isCancelled 로 수동 확인 |
취소 체크 및 에러 던지기 | try Task.checkCancellation() |
에러 처리 | CancellationError 로 잡아서 분기 가능 |
✅ 중첩된 Task와 취소 전파
1. 중첩된 Task는 별도의 컨텍스트로 동작
- Swift에서 Task는 기본적으로 새로운 독립적인 실행 컨텍스트를 만든다.
- 즉, 상위 Task 내에서 Task { }로 생성한 Task는 부모와 별개임.
- 그래서 부모 Task가 취소되더라도, 내부에서 만든 Task에는 취소가 전파되지 않는다.
swift
복사편집
func parentTask() async {
Task {
// 이 Task는 부모와 별개
// 부모가 취소돼도 여기까지는 취소가 전파되지 않음
await doWork()
}
}
- 위 예제에서 내부 Task는 **“중첩되어 있지만 독립적”**인 구조.
- 취소 전파가 안 되는 이유는 같은 구조적 컨텍스트가 아니기 때문.
✅ 구조적 동시성에서는 취소가 제대로 전파됨
📌 예시 1: async let 사용 시
swift
복사편집
func example() async {
async let result1 = doSomething()
async let result2 = doSomethingElse()
// 부모가 취소되면 result1, result2 도 함께 취소됨
let _ = await (try? result1, try? result2)
}
- async let은 parent-child 관계가 유지되며, 구조적으로 연결되어 있음
- 부모가 취소되면 자식도 취소됨
📌 예시 2: withTaskGroup
swift
복사편집
func example() async {
await withTaskGroup(of: Void.self) { group in
group.addTask {
await workA()
}
group.addTask {
await workB()
}
group.cancelAll() // → 모든 작업에 취소가 전파됨
}
}
✅ 직접적인 취소 처리를 구현하지 않아도 되는 경우
취소에 대응하는 API는 자동으로 취소됨
- 예를 들어 URLSession.shared.data(from:) 같은 API는 내부적으로 CancellationError를 던질 준비가 되어 있는 구조야.
- 즉, 우리가 따로 Task.isCancelled를 확인하지 않아도, Task가 취소되면 이 함수가 자동으로 취소되고 에러를 throw함.
func fetchData(from url: URL) async throws -> Data {
let (data, _) = try await URLSession.shared.data(from: url)
return data
}
- 이 작업이 포함된 Task가 취소되면, data(from:)가 취소되고 CancellationError가 throw됨.
- 별도의 checkCancellation() 필요 없음.
- Task.sleep 도 이런 경우