Swift Task.cancel()이 바로 멈추지 않는 이유와 취소 처리 예제
Task.cancel()은 실행 중인 코드를 강제로 끊는 명령이 아니에요. 작업에 취소를 요청하고, 작업이 그 신호를 확인해 종료하도록 하는 협력적 취소 방식이에요. 반복문에 취소 확인이 없거나 try?로 취소 오류를 삼키면 이후 코드가 계속 실행될 수 있어요. 해결하려면 취소를 확인할 지점과 실제로 함수를 빠져나갈 경로를 함께 만들어야 해요. Apple Task.cancel() 문서

취소를 무시하는 반복문과 대응하는 반복문
// CancellationDemo.swift
// 컴파일: swiftc -parse-as-library CancellationDemo.swift -o CancellationDemo
// 실행: ./CancellationDemo
@main
struct CancellationDemo {
static func main() async throws {
try await run(cooperative: false)
try await run(cooperative: true)
}
static func run(cooperative: Bool) async throws {
let label = cooperative ? "cooperate" : "ignore"
print("\n[\(label)] 시작")
let task = Task<Void, Error> {
defer { print("[\(label)] 작업 본문에서 나감") }
for item in 1...5 {
if cooperative {
try Task.checkCancellation()
try await Task.sleep(nanoseconds: 100_000_000)
try Task.checkCancellation()
} else {
// 잘못된 예: 취소 오류를 버리고 계속 진행해요.
try? await Task.sleep(nanoseconds: 100_000_000)
}
print("[\(label)] 항목 \(item) 처리")
}
if cooperative {
try Task.checkCancellation()
}
print("[\(label)] 결과 반영")
}
try await Task.sleep(nanoseconds: 250_000_000)
print("[\(label)] 취소 요청")
task.cancel()
print("[\(label)] 취소 상태: \(task.isCancelled)")
do {
try await task.value
print("[\(label)] 정상 반환")
} catch is CancellationError {
print("[\(label)] 취소 오류 수신")
} catch {
print("[\(label)] 다른 오류: \(error)")
}
print("[\(label)] 종료 확인")
}
}
try?가 지운 것은 취소 상태가 아니라 오류예요
‘취소를 무시하는 반복문과 대응하는 반복문’ 예제에서 ignore 모드는 일부러 잘못된 처리를 보여줘요. Task.sleep은 대기 중 취소되면 CancellationError를 던지지만, try?는 그 오류를 nil로 바꿔요. 반환값을 사용하지 않으므로 다음 print와 반복이 그대로 이어져요. 취소 상태를 해제한 것은 아니에요. Apple Task.sleep 문서 Swift 표현식 문서
같은 문제가 catch에서도 생길 수 있어요. 오류를 로그로만 남긴 뒤 catch 바깥에서 결과를 저장한다면, 취소된 작업도 저장 경로에 도달해요. 취소를 처리하는 분기에서는 return으로 종료하거나 오류를 다시 던지는 식으로 후속 작업에 진입하지 않도록 구성하세요.

오류로 끝낼지, 일부 결과를 돌려줄지 정해요
작업 전체를 실패로 끝내야 한다면 try Task.checkCancellation()이 간단해요. 취소됐을 때 CancellationError를 던지므로 호출 경로가 오류를 전파하도록 만들면 돼요. 반대로 이미 처리한 항목만 반환하는 함수라면 if Task.isCancelled { return partialResults }처럼 직접 종료 정책을 표현할 수 있어요. 두 방식 모두 공식 문서가 설명하는 취소 대응이에요. Swift 동시성 문서
긴 계산에서는 작은 처리 단위의 경계에 확인을 두는 방법을 권해요. 항목 하나를 처리하는 데 오래 걸린다면 반복문 시작에서만 확인해도 그 항목 내부까지 즉시 중단되지는 않아요. 오래 걸리는 내부 계산도 나눌 수 있는지 살펴보세요. await라는 표기만으로 취소가 처리되는 것도 아니므로, 호출하는 함수의 취소 동작을 확인해야 해요. Apple Task 문서
결과를 반영하기 직전의 확인은 불필요한 반영을 줄이는 데 유용하지만, 확인과 반영을 하나의 원자적 동작으로 만들어 주지는 않아요. 마지막 확인을 지난 뒤 취소될 수도 있어요. 검색 화면에서 ‘가장 최근 요청의 결과만 표시’해야 한다면, 제안하는 추가 조건은 요청 식별자를 현재 요청과 대조한 뒤 같은 actor의 중단 없는 구간에서 반영하는 거예요. 취소만으로 최신 결과 여부까지 판정하지는 마세요. Apple Task.cancel() 문서
로그에서는 요청과 종료를 따로 확인해요
예제를 CancellationDemo.swift로 저장하고 Swift 6 명령줄 환경에서 swiftc -parse-as-library CancellationDemo.swift -o CancellationDemo로 컴파일한 뒤 ./CancellationDemo로 실행하세요. 예제의 대기 시간은 취소가 반복 도중 들어오도록 구성한 값이에요. 스케줄링에 따라 취소 전 출력되는 항목 수는 달라질 수 있으니, 특정 번호에서 멈춘다고 가정하지 마세요.
확인할 것은 cancel() 직후의 isCancelled 값만이 아니에요. ignore 모드에서는 취소 이후에도 후속 출력이 가능한지, cooperate 모드에서는 취소 감지 후 ‘결과 반영’에 도달하지 않는지를 보세요. ‘종료 확인’은 task.value를 기다린 다음 출력하도록 구성했어요. 작업이 취소 전에 이미 완료됐다면 정상 완료 로그가 나올 수 있어요. 취소 요청은 끝난 작업을 되돌리지 않아요. Apple Task 문서 Apple Task.cancel() 문서
실제 코드에 적용할 때는 작업을 새로 만드는 위치도 살펴보세요. async let이나 작업 그룹의 자식 작업에는 부모의 취소가 전달되지만, 함수 안에서 별도로 만든 Task { ... }는 구조화된 자식 작업이 아니에요. 별도 Task가 계속 실행된다면 그 핸들을 보관하고 필요한 시점에 직접 취소하도록 수명을 관리하세요. Swift 동시성 문서