← Study Hub

04 · 협력적 취소

checkCancellation vs isCancelled, 그리고 취소 핸들러가 필요한 순간
진행률
0 / 0 완료

← 문항 인덱스

면접 질문
  1. Task.checkCancellation()Task.isCancelled의 차이와 각각의 사용 시점은?
  2. withTaskCancellationHandler는 언제 필요한가요?

답변 ① — throw로 끊을 것인가, 내가 마무리할 것인가 🔥 핵심

전제부터 짚으면, Swift의 취소는 협력적(cooperative)입니다. 취소는 강제 중단이 아니라 "그만해 달라"는 플래그일 뿐이고, 코드가 스스로 확인해야 멈춥니다. 두 API는 그 확인 결과를 어떻게 처리하느냐에서 갈립니다.

선택 기준은 간단합니다. 그냥 끊어도 되면 checkCancellation(), 끊기 전에 뭔가 해야 하면 isCancelled입니다. 어느 쪽이든 비싼 작업의 자연스러운 분기점(작업 전후)에 넣어 줍니다.

목적이 다르다 — throw로 끊기 vs 부분 결과 반환 (Swift)
// checkCancellation: 취소면 throw로 즉시 탈출
func process(_ items: [Item]) async throws {
    for item in items {
        try Task.checkCancellation()       // 취소 시 CancellationError
        await handle(item)
    }
}

// isCancelled: 지금까지 모은 부분 결과를 돌려주고 싶을 때
func loadAll(_ ids: [ID]) async -> [Item] {
    var result: [Item] = []
    for id in ids {
        if Task.isCancelled { return result }   // 여기까지만 반환(throw 안 함)
        result.append(await load(id))
    }
    return result
}

답변 ② — 잠들어 있는 동안 온 취소에 반응해야 할 때

withTaskCancellationHandler는 task가 suspend되어 멈춰 있는 동안 취소가 와도 즉시 부수효과를 실행해야 할 때 필요합니다. 대표적으로 URLSession task를 즉시 취소하거나, delegate·continuation 기반 작업을 취소 시점에 에러로 resume해서 매달린 task를 풀어 주는 경우입니다.

isCancelled 폴링으로는 안 될까요? 폴링은 코드가 실행 중일 때만 확인할 수 있습니다. await에서 잠들어 있으면 확인할 코드 자체가 돌지 않으므로, 콜백 형태의 훅이 없으면 깨어날 때까지 아무 반응도 못 합니다. 콜백이 영영 안 오는 API라면 영원히 못 깨어납니다.

⚠️ 주의

핸들러는 @Sendable이고 취소를 호출한 스레드에서 operation과 동시에 실행됩니다. 따라서 스레드 안전한 상태(actor·락·continuation)만 건드려야 합니다. 또 진입 시 이미 취소된 상태라면 즉시 실행되고, 최대 1회 호출되며, operation을 강제로 멈추지는 않습니다 — 여전히 코드가 isCancelled를 확인해야 합니다.

자는 사이에 온 취소에 반응한다 (Swift)
func download(_ url: URL) async throws -> Data {
    let task = URLSession.shared.dataTask(with: url)

    return try await withTaskCancellationHandler {
        try await withCheckedThrowingContinuation { cont in
            // task 완료 시 cont.resume(...) 으로 연결
            task.resume()
        }
    } onCancel: {
        task.cancel()      // @Sendable · 취소한 스레드에서 즉시 실행 · 최대 1회
                           // ← 이게 없으면 취소해도 다운로드가 계속 돈다
    }
}

쉽게 이해하기

취소는 "야, 이제 그만할래?"라고 물어보는 것이지, 하던 걸 억지로 뽑아버리는 게 아니에요. 그래서 내가 중간중간 물어봐야 멈춥니다. 아무도 안 물어보면 계속 일해요.

(질문 ①) 물어본 다음이 갈립니다. checkCancellation()은 그만이면 그 자리에서 손 놓고 나가버리는 것이고, isCancelled"알겠어, 그럼 여기까지만 정리하고 갈게"라며 하던 걸 마무리하는 거예요. 지금까지 받은 것만 화면에 보여주고 싶다면 두 번째를 써야겠죠.

(질문 ②) 그런데 내가 자고 있으면(다운로드를 기다리며 멈춰 있으면) 스스로 물어볼 수가 없어요. 이때 필요한 게 withTaskCancellationHandler, 즉 알람입니다. 취소가 오면 알람이 대신 울려서 "지금 당장 다운로드부터 꺼!"를 처리해 줘요. 알람이 없으면, 취소 버튼을 눌러도 다운로드는 끝날 때까지 계속 돕니다.

🔑 한 문장

취소는 명령이 아니라 부탁이다. 깨어 있을 땐 내가 확인하고, 자고 있을 땐 알람이 대신 처리한다.