이전과 차이점
기존 GCD를 이용했을 때는 작업을 취소하기가 거의 불가능했거나 많이 번거로웠다.
하지만 Swift Concurrency에서는 작업의 취소가 굉장히 편해졌다.
이번 포스팅부터는 컨커런시에서 취소하는 법 정확히는 취소를 전파하고 그 취소에 대한 핸들링하는 법을 알아보자.
종류
1. 명시적 취소
cancel() 메서드를 통해 자식 작업들에게 취소를 전파
2. 암시적 취소
자식 작업에서 에러가 발생했을 때
특징
1. 작업 취소가 가능한 구조는 다음과 같다.
- 동일한 context , Task 안의 Task 또는 Task.detached는 해당되지 않음
- 구조적 동시성 구조 , async let or TaskGroup
2. 작업의 취소는 즉각 취소가 아닌 취소되었음을 알려는 행위다.
- task.canecl()을 통해 Task.isCancelled 속성을 true로 변경시킨다.
- Task.isCancelled 속성을 통해 작업이 취소 되었는 지 알려준다.
let task = Task(priority: .medium) {
sleep(1)
let datas = await multiFetchDatas()
}
sleep(6)
task.cancel() // 6 초후 Task.isCancelled 값을 true로 전파
3. 실제로 작업 취소를 하려면 별도의 처리가 필요하다.
- 취소 되기전에 해야할 행동 역 Error를 던지기전에 처리한다.
- 이 때 던지는 에러는 CancellationError으로 많이 쓴다.
- 커스텀 에러를 던져도 된다.
func asyncFetchData() async throws -> Data {
print("실행")
guard Task.isCancelled else {
print("취소됨")
// 취소되기전 할 작업이 있다면 ..
throw CancellationError() // 또는 커스텀 에러
}
...
}
- 위 처럼 guard와 CancellationError를 쓰기 귀찮고 취소되면 바로 Error를 리턴하고 싶으면 Task.checkCancellation()을 사용
func asyncFetchData() async throws -> Data {
print("실행")
try Task.checkCancellation() // Task.isCancelled가 true가 되면 바로 Error 리턴
...
}
4. 한번 취소된 작업은 다시 재개될 수 없다.
5. 취소된 것까지의 작업 결과를 사용하기 위해서는 Error 보단 nil을 이용
- 작업이 취소될 때 위른 Error를 던져 위에 알렸지만 지금까지 한 작업의 결과가 필요하다면 옵셔널을 이용하자.
- try? 와 옵셔널 바인딩을 이용해서 작업이 취소되면 nil이 오는 특징을 살려 끝난 결과만이라도 살릴 수 있다.
func multiFetchDataWithTaskGroup() async -> [Data?] {
return await withTaskGroup(of: Data?.self, returning: [Data?].self) { group in
for _ in 0..<3 {
group.addTask(priority: .background) {
return try? await asyncFetchData() // Error일 경우 nil
}
}
var result: [Data?] = []
for await data in group {
if let data { // nil이 아닐 때만 append
result.append(data)
}
}
return result
}
}
실제 사례
SwiftUI task 메서드
Adds an asynchronous task to perform before this view appears.
nonisolated
func task(
priority: TaskPriority = .userInitiated,
_ action: @escaping () async -> Void
) -> some View
- swiftUI에서 onAppear 때 비동기 context를 사용할 때 보통 Task {}를 이용했었다.
}.onAppear {
Task {
// do SomeThing
}
}
- 하지만 task 메서드를 통해 더 쉽고 효율적으로 사용할 수 있다.
.task(priority: <#T##TaskPriority#>) {
// doSomething
}
- 여기서 task 메서드의 가장큰 장점은 별도의 취소를 안해도 화면이 사라지면 알아서 취소를 해준다는 점
취소 핸들러
개요
위 그림을 통해 보면 상위 작업 또는 계층에서 아래의 작업이 Error를 던져주기 전까지는 작업이 끝나지 않고 있다.
취소 핸들러를 이용하면 작업이 취소되는 즉시 감지할고 동작할 수 있게 도와주는 녀석이 있는데
바로 그게 취소핸들러다.
정의
execute an operation with a cancellation handler that’s immediately invoked if the current task is canceled.
현재 작업이 취소되자마자 즉시 실행되는 핸들러
func withTaskCancellationHandler<T>(
operation: () async throws -> T, // 비동기 또는 동기 코드
onCancel handler: () -> Void, // 즉시 동작할 핸들러
isolation: isolated (any Actor)? = #isolation
) async rethrows -> T
특징
- 현재 작업이 취소되자마자 즉시 실행됨
- 하위 작업에서 Error가 전달될 때까지 기다리지 않고 바로 실행됨
예제 코드
let value = await withTaskCancellationHandler {
try? await Task.sleep(for: .seconds(3))
return 1
} onCancel: {
print("핸들러 코드")
}
참고
'iOS > Swift Concurrency' 카테고리의 다른 글
구조적 동시성 (1) (0) | 2024.10.26 |
---|---|
Continuation (0) | 2024.10.25 |
async await (0) | 2024.10.24 |
swift concurrency 등장배경 (0) | 2024.10.24 |
Task (0) | 2024.10.23 |