SC #8: Task 취소
Source: Dev.to
참고: Swift에서
Task를 취소한다고 해서 실행이 즉시 중단된다는 보장은 없습니다. 각Task는 수동으로 취소 여부를 확인해야 합니다(예:Task.checkCancellation()또는Task.isCancelled사용).
1. 기본 예제: 이미지 다운로드
func fetchImage() async throws -> UIImage? {
let imageTask = Task { () -> UIImage? in
let imageURL = URL(string: "https://httpbin.org/image")!
var imageRequest = URLRequest(url: imageURL)
imageRequest.allHTTPHeaderFields = ["accept": "image/jpeg"]
print("Starting network request...")
let (imageData, _) = try await URLSession.shared.data(for: imageRequest)
return UIImage(data: imageData)
}
// La tarea se cancela inmediatamente después de ser creada.
imageTask.cancel()
// Esperamos el valor (o el error) de la tarea.
return try await imageTask.value
}
무슨 일이 일어나는가
| 단계 | 설명 |
|---|---|
| 1️⃣ | URLSession.shared.data(for:) 호출을 UIImage?를 반환하는 Task 안에 감싸습니다. |
| 2️⃣ | imageTask.cancel() 로 작업을 취소합니다. |
| 3️⃣ | try await imageTask.value 로 결과를 기다립니다. |
| 4️⃣ | 네트워크 요청이 취소가 적용되기 전에 시작되지만, URLSession은 취소 신호를 존중하고 요청을 중단합니다. |
콘솔 출력
Starting network request...
Image loading failed: Error Domain=NSURLErrorDomain Code=-999 "cancelled"
2. Task.checkCancellation()
Task.checkCancellation() 은 작업이 취소된 경우 자동으로 CancellationError 를 발생시킵니다.
func fetchImage() async throws -> UIImage? {
let imageTask = Task { () -> UIImage? in
let imageURL = URL(string: "https://httpbin.org/image")!
// ...
try Task.checkCancellation() // 작업이 이미 취소된 상태에서 `checkCancellation()`을 호출했기 때문에 웹 요청이 수행되지 않습니다.
}
}
3. Task.isCancelled
Task.isCancelled는 예외를 발생시키지 않고 취소 상태를 검사할 수 있게 해줍니다; 우리는 수동으로 처리할 수 있습니다.
func fetchImage() async throws -> UIImage? {
let imageTask = Task { () -> UIImage? in
let imageURL = URL(string: "https://httpbin.org/image")!
// ...
guard Task.isCancelled == false else {
print("Image request was cancelled")
return nil
}
let (imageData, _) = try await URLSession.shared.data(for: imageRequest)
// ...
}
imageTask.cancel()
return try await imageTask.value
}
출력
Image request was cancelled
4. async 함수를 Task로 감싸야 할까요?
작업을 의도적으로 취소하고 싶을 때만 명시적으로 Task를 생성하면 됩니다. 수동 취소가 필요하지 않다면, 래퍼 없이 함수를 작성할 수 있습니다:
func fetchImage() async throws -> UIImage? {
let imageURL = URL(string: "https://httpbin.org/image")!
var imageRequest = URLRequest(url: imageURL)
imageRequest.allHTTPHeaderFields = ["accept": "image/jpeg"]
try Task.checkCancellation()
let (imageData, _) = try await URLSession.shared.data(for: imageRequest)
try Task.checkCancellation()
return UIImage(data: imageData)
}
5. SwiftUI에서 수동 취소
5.1 작업 참조 저장
@State private var image: UIImage?
@State private var imageDownloadingTask: Task?
var body: some View {
VStack {
if let image {
Image(uiImage: image)
} else {
Text("Loading...")
}
}
.onAppear {
imageDownloadingTask = Task {
do {
image = try await fetchImage()
print("Image loading completed")
} catch {
print("Image loading failed: \(error)")
}
}
}
.onDisappear {
imageDownloadingTask?.cancel()
}
}
5.2 .task(priority:_: ) 수정자 사용
SwiftUI는 뷰가 사라질 때 작업을 자동으로 생성하고 취소합니다.
@State private var image: UIImage?
var body: some View {
VStack {
if let image {
Image(uiImage: image)
} else {
Text("Loading...")
}
}
.task {
do {
image = try await fetchImage()
print("Image loading completed")
} catch {
print("Image loading failed: \(error)")
}
}
}
중요:
.task내부의 코드는.onAppear에서 실행되는 모든 코드보다 먼저 실행됩니다. 그러나 작업이 정확히 언제 실행되는지는 보장되지 않으며,.onAppear의 동기 코드가.task작업보다 먼저 실행될 수 있습니다.
6. 작업 계층 구조에서 취소 (슈퍼 작업 → 하위 작업)
슈퍼 작업이 취소되면, 모든 하위 작업에 취소 신호가 전달됩니다. 각 하위 작업은 자신의 상태를 명시적으로 확인해야 합니다 (isCancelled 또는 checkCancellation()). 확인하지 않으면 계속 실행됩니다.
예시
func parentTask() async {
let handler = Task {
print("Parent task started")
async let child1 = childTask(id: 1)
async let child2 = childTask(id: 2)
// 두 하위 작업이 끝날 때까지 기다립니다.
_ = try await [child1, child2]
}
// 일부 작업을 시뮬레이션한 뒤 취소합니다.
try? await Task.sleep(nanoseconds: 200_000_000) // 0.2 s
handler.cancel()
do {
_ = try await handler.value
} catch let error as CancellationError {
print("La tarea fue cancelada")
} catch {
print("Hubo otro error")
}
print("Parent task finished")
}
func childTask(id: Int) async throws -> Int {
for i in 1..<4 {
try Task.checkCancellation() // <‑‑ 취소 여부 확인
try await Task.sleep(nanoseconds: 100_000_000) // 0.1 s
print("Subtarea: \(id), paso: \(i)")
}
print("Terminé la subtarea \(id)")
return id
}
가능한 결과
- 슈퍼 작업이 하위 작업이 끝나기 전에 취소되면,
childTask의 각 반복이CancellationError를 발생시키고 실행이 중단됩니다. - 어떤 하위 작업이 취소를 확인하지 않으면, 슈퍼 작업이 취소되었음에도 계속 실행됩니다.
7. 빠른 요약
| 도구 | 무엇을 하는가 | 예외를 발생시키나요? |
|---|---|---|
task.cancel() | 작업을 중단해야 함를 표시합니다. | 아니오 |
Task.checkCancellation() | 작업이 취소된 경우 CancellationError를 발생시킵니다. | 예 |
Task.isCancelled | 작업이 취소된 경우 true를 반환합니다. | 아니오 (guard/if에서 사용). |
.task {} (SwiftUI) | 뷰가 사라질 때 자동으로 취소되는 작업을 생성합니다. | 아니오 (하지만 내부 작업은 예외를 발생시킬 수 있음). |
이 개념들을 통해 제어하고 최적화하여 Swift 및 SwiftUI 애플리케이션에서 비동기 작업의 취소를 관리할 수 있습니다. 코딩하세요!
btarea: \(id)")
return id
}