작업 취소란 Task 내부의 비동기 함수들에게 취소 신호를 전파하는 것을 말한다.
구조적 동시성에서 부모 작업은 자식 작업에도 취소를 전파한다.
여기서 취소는 작업을 즉시 중단시키는 강제 종료가 아니라, 각 작업이 취소 신호를 확인하고 스스로 실행을 중단하도록 하는 협력적 취소 Cooperative Cancellation 방식으로 동작한다.
func fetch() async throws -> UIImage? {
try await Task.sleep(nanoseconds: 1_000_000_000)
return UIImage(named: "")
}
let task = Task {
for i in 1...10 {
print("작업 \(i) 진행 중...")
try await fetch()
}
}
try await Task.sleep(nanoseconds: 2_000_000_000)
task.cancel()
let task = Task {
async let data1 = fetch()
async let data2 = fetch()
async let data3 = fetch()
// 부모 Task가 취소되면 data1, data2, data3 모두 자동 취소됨
return try await [data1, data2, data3]
}
try await Task.sleep(nanoseconds: 500_000_000)
task.cancel()
let task = Task {
try await withThrowingTaskGroup(of: UIImage?.self) { group in
for i in 1...10 {
group.addTask {
let image = try await fetch()
print("작업 \(i) 완료")
return image
}
}
var results: [UIImage?] = []
for try await result in group {
results.append(result)
}
return results
}
}
try await Task.sleep(nanoseconds: 500_000_000)
task.cancel()
TaskGroup에선 그룹 자체에서도 모든 작업을 취소 할 수도 있다.
이것으로 어떤 한개의 작업이 성공했을때 나머지 작업을 취소한다던지, 어떤 한개의 작업이 실패했을때 나머지 작업을 취소한다던지 조건부 취소도 가능하다.
func fetchData(from: String) async throws -> Data {
try await Task.sleep(nanoseconds: 1_000_000_000)
return Data()
}
func fetchFromMultipleServers() async throws -> Data {
return try await withThrowingTaskGroup(of: Data.self) { group in
let servers = ["server1.com", "server2.com", "server3.com"]
// 모든 서버에 동시 요청
for server in servers {
group.addTask {
try await fetchData(from: server)
}
}
// 첫 번째 성공한 결과를 받으면 나머지 취소
guard let firstResult = try await group.next() else {
throw NSError(domain: "", code: 00)
}
// 나머지 작업들 모두 취소
print("\(firstResult)")
group.cancelAll()
return firstResult
}
}
Task {
try await fetchFromMultipleServers()
}
취소 상태를 Boolean 값으로 확인한다. 가장 기본적인 방법이다.
func processItems() async throws {
for i in 1...100 {
// 취소 확인
print("작업 \(i)")
if Task.isCancelled {
print("작업이 취소되었습니다.")
return // 직접 종료
}
// 실제 작업 수행
try await fetch()
}
}
let task = Task {
try await processItems()
}
Task {
try await Task.sleep(nanoseconds: 5_000_000_000)
task.cancel()
}
취소되었다면 자동으로 CancellationError를 던진다. throws 함수에서 사용하기 좋다.
func fetchData() async throws -> [String] {
var results: [String] = []
for i in 1...100 {
// 취소되면 자동으로 CancellationError 발생
print("작업 \(i)")
try Task.checkCancellation()
try await Task.sleep(nanoseconds: 1_000_000_000)
results.append("data")
}
return results
}
let task = Task {
do {
try await fetchData()
} catch {
print("error: \(error)")
}
}
Task {
try await Task.sleep(nanoseconds: 5_000_000_000)
task.cancel()
}