iOS/Swift Concurrency
[스터디 숙제] 취소
Hamp
2025. 8. 30. 15:35
반응형

👋 들어가기 전
컨커린시 2주차, 첫 숙제는 바로 "취소"에 대해 정리해보는 것
이전에 독학으로 학습한 내용은 링크 에서 다뤘고
이번에는 swift proposlas 기반으로 recap을 한번해보자.
🏁 학습할 내용
- 취소 전파되는 경우
- 취소의 특성
- 비동기 함수의 취소 대응 방식
- 취소 시, 즉시 일어나는 영향
- 예제로 살펴보는 Task 취소
🚨 취소 전파되는 경우
A task can be cancelled asynchronously by any context that has a reference to a task or one of its parent tasks. Cancellation can be triggered explicitly by calling cancel() on the task handle.
Cancellation can also trigger automatically, for example when a parent task throws an error out of a scope with unawaited child tasks.
- Task는 부모 Task 또는 현재 Task에 대한 참조를 가진 어떤 context에서도 Task를 비동기적으로 취소할 수 있다.
- 취소는 크게 2가지 방법이 있다.
- 명싱적취소: task handle에서 cancel() 메서드를 호출하는 것
- 암시적 취소: 상우 작업에서 error를 던졌을 때, 아직 하위작업이 남아있다면, 하위 작업도 취소가 전파됨
💡 task handle이란?
Task 객체를 변수에 담는 행위 ( let task = Task...), Task 핸들을 했더라도, 나중에 nil을 하지 않으면
Task Leak으로 이어질 수 있다.


📌 취소 특징
The effect of cancellation within the cancelled task is fully cooperative and synchronous. That is, cancellation has no effect at all unless something checks for cancellation. Conventionally, most functions that check for cancellation report it by throwing CancellationError(); accordingly, they must be throwing functions, and calls to them must be decorated with some form of try.
As a result, cancellation introduces no additional control-flow paths within asynchronous functions; you can always look at a function and see the places where cancellation can occur. As with any other thrown error, defer blocks can be used to clean up effectively after cancellation.
- 취소는 완전히 협력적이고 동기적으로 동작한다.
- 취소를 확인하지 않으면, 취소는 아무런 영향을 주지 않는다.
- 취소 여부를 확인하는 대부분의 함수는 CancellationError를 던져 보고하므로, 반드시 throwing 함수여야하며, 호출 시 try 구분이 필수
- 덕분에 비동기 함수 내에서 취소는 새로운 제어 흐름이 필요없음
- defer 블럭을 이용해 취소 이후에도 필요한 정리도 가능함
🤝 비동기 함수의 취소 대응 방식
With that said, the general expectation is that asynchronous functions should attempt to respond to cancellation by promptly throwing or returning. In most functions, it should be sufficient to rely on lower-level functions that can wait for a long time (for example, I/O functions or Task.value) to check for cancellation and abort early.
Functions which perform a large amount of synchronous computation may wish to periodically check for cancellation explicitly
- 일반적으로 비동기 함수는 취소에 대해, 신속히 반환 또는 에러를 던지는 것을 기대한다.
- 대부분의 함수는 I/O 작업같은 오랫동안 대기하는 하위 레벨 함수에 의존하여, 취소 여부를 판단하고 조기에 중단을 권장
- 하지만, 동기적으로 오래걸리는 연산을 수행한다면, 주기적으로 취소여부를 판단해라
🔥 취소 시, 즉시 일어나는 영향
Cancellation has two effects which trigger immediately with the cancellation
A flag is set in the task which marks it as having been cancelled; once this flag is set, it is never cleared. Operations running synchronously as part of the task can check this flag and are conventionally expected to throw a CancellationError.
Any cancellation handlers which have been registered on the task are immediately run. This permits functions which need to respond immediately to do so.
취소 플래그 심기
- Task 내부에 취소 플래그를 셋
- 한번 설저된 플래그는 절대 사라지지 않음
- Task안의 동기적인 연산은 모두 이 플래그를 확인할 수 있고, 대부분 CanecellationError를 던지는 방식으로 반응함
취소 핸들러 실행
- Task에 등록된 취소 핸들러가 즉시 실행되어 즉각적인 반응을 반드시 해야함
🤖 예제로 살펴보는 Task 취소
1️⃣ 앞의 자식 작업에서 에러났을 때
func chopVegetables() async throws -> [Vegetable] {
return try await withThrowingTaskGroup(of: Vegetable.self) { group in
var veggies: [Vegetable] = []
group.addTask {
try await chop(Carrot()) // (1) throws UnfortunateAccidentWithKnifeError()
}
group.addTask {
try await chop(Onion()) // (2)
}
for try await veggie in group { // (3)
veggies.append(veggie)
}
return veggies
}
}
- Carrot과 Onion이 child 작업으로 추가됨
- 이 때, 결과는 for try.. 구문에서 받음
- Carrot에서 어떤 에러가 발생한다고 가정함, 그리고 Carrot 작업이 먼저 끝난다고 가정
- Carrot에서 에러가 발생했으므로, Carrot의 부모에도 취소가 전달됨
- 부모가 Oinon 작업에도 취소 전파하여, Onion 작업도 자동적으로 취소됨, 비록 작업이 완료되더라도 결과는 버려짐
- 구조적 동시성은 자식 Taskrk 부모 Task보다 오래 남는걸 허락하지 않음
- 그렇기 때문에, 부모는 자식 Task가 모두 종료될 때까지 반한되지 않음
2️⃣ 취소는 플래그만 전달한다.
func chop(_ vegetable: Vegetable) async throws -> Vegetable {
try Task.checkCancellation() // 자동으로 CancellationError를 던짐
// chop chop chop ...
// ...
guard !Task.isCancelled else {
print("Cancelled mid-way through chopping of \(vegetable)!")
throw CancellationError()
}
// chop some more, chop chop chop ...
}
- Task.checkCancellation()을 호출하면, Task가 취소 상태라면 즉시 CancellationErorr를 던진다.
- 또는 Task.isCancelled를 직접 확인해서, 취소 여부를 판단할 수 있다.
- 이 때, 어떤 이유로 Task를 취소했는지, 전달할 수 없다
- Task는 취소 여부만 전달하지, Task 간의 메시지를 전달하는 통신 수단이 아니다.
출처
swift-evolution/proposals/0304-structured-concurrency.md at main · swiftlang/swift-evolution
This maintains proposals for changes and user-visible enhancements to the Swift Programming Language. - swiftlang/swift-evolution
github.com
반응형