Skip to content

작업 취소 #8

@camosss

Description

@camosss

작업 취소 (Task Cancellation)

  • 즉시 강제 종료가 아닌, 취소 전파

  • 취소되면 Task.isCancelled == true로, 실제 작업 중단은 코드에서 구현 필요

  • 구조적 동시성에서는 부모 Task가 취소되면 자식(async let, TaskGroup)에도 취소가 전파

  • 한 번 취소된 Task는 되돌릴 수 없음

부모 Task (cancel)
 ├─ 자식 A  ← 취소 전파
 ├─ 자식 B  ← 취소 전파
 └─ 자식 C  ← 취소 전파

취소 확인

  • task.cancel() → Task에 취소 신호 전송
  • Task.isCancelled → 현재 Task가 취소되었는지 확인
let task = Task {
    while !Task.isCancelled {
        // 반복해서 일 수행
        try await Task.sleep(for: .milliseconds(50))
    }
    // 루프 빠져나오면(Task.isCancelled == true) 정리 후 종료
}

print(task.isCancelled) // false
task.cancel()           // 신호 전파
print(task.isCancelled) // true
  • try Task.checkCancellation() → 취소 시 CancellationError 던짐
try Task.checkCancellation() // 여기서 바로 throw → 아래 코드 실행 안 됨

취소 핸들러

  • withTaskCancellationHandler(operation:, onCancel:)
  • 취소 시 onCancel 클로저는 즉시 실행됨
  • 내부 동작이 async가 아니어도 정리 작업 가능
let task = Task {
    try await withTaskCancellationHandler(operation: {
        // 본 작업
        try await Task.sleep(for: .seconds(5))
    }, onCancel: {
        // 취소되면 즉시 실행
    })
}

task.cancel() 

기본적으로 취소를 지원하는 API

  • Task가 취소 상태로 바뀌면 자동으로 에러(CancellationError)를 던지고 종료
  • 별도 체크 없이도 작업이 중단

URLSession

  • URLSession.shared.data(from:) 내부는 취소를 지원
  • task.cancel()이 호출되면, 중간에 CancellationError를 던지고 네트워크 요청을 취소 → Task 종료
let task = Task {
    let url = URL(string: "https://example.com")!
    let (data, _) = try await URLSession.shared.data(from: url)
    // 네트워크 응답
}

// 바로 취소
task.cancel()

Task.sleep

  • 취소되면 기다리던 중간에 CancellationError 던지고 종료
let task = Task {
    try await Task.sleep(for: .seconds(5))
}

Task {
    try await Task.sleep(for: .seconds(2))
    task.cancel() // 2초 후 취소
}

구조적 동시성과 취소 전파

async let

  • 부모 Task가 취소되면 자식도 취소됨
  • 하나의 자식에서 에러 발생 → 다른 자식에게도 취소 전파
Task {

    // a, b, c는 동시 실행 → 결과는 나중에 await 시점
    async let a = fetchA()   // async throws
    async let b = fetchB()   // async throws
    async let c = fetchC()   // async throws

    do {
        _ = try await (a, b, c)
    } catch {
        // 여기 들어오면 나머지 미완료 자식에 취소 전파
        // ex. b가 네트워크 에러를 던지면, try await (a, b, c) 전체가 throw
        // -> 나머지 실행 중인 자식(a, c)은 자동으로 취소 신호를 받음
    }
}

TaskGroup

  • 부모 취소 -> 그룹 전체 자식 취소
  • 에러 전파 시에도 나머지 작업들이 자동 취소
  • 필요 시 명시적으로 group.cancelAll() 가능
// `fetchAll`을 호출한 쪽에서 Task를 취소하면,
// `withThrowingTaskGroup` 안의 모든 자식 Task도 자동으로 취소 신호를 받음

func fetchAll(urls: [String]) async throws -> [UIImage] {
    try await withThrowingTaskGroup(of: UIImage.self) { group in
        for url in urls {
            group.addTask {
                try await fetchImage(url) // 자식 중 하나라도 throw → 나머지 자동 취소
            }
        }

        var results: [UIImage] = []
        for try await image in group {
            results.append(image)
        }
        return results
    }
}

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions