Structured Concurrency / Unstructured Concurrency
Concurrency는 뭔가 아~~하고 나서 또 안보면 까먹는 그런 친구같네..
간단한게만 살펴보자~~
# Task
비동기 작업 단위 (A unit of asynchronous work)
모든 비동기(asynchronous) 코드는 어떠한 Task의 일부로 실행된다.
# Structured Concurrency / Unstructured Concurrency
우선 결론!! 이미지로 간단하게 보자면 아래와 같다.
# Structured Concurrency(구조화된 동시성)
[Swift에서 Structured Task를 만드는 방법]
1. async let ➡️ 내부적으로 Child Task 생성
2. TaskGroup ➡️ 명시적으로 Child Task를 추가할 수 있음
[Structured Task? Structured Concurrency?]
async let, TaskGroup.. 일단 두개가 뭔지는 몰라도 괜찮고, 우선 Child Task가 만들어진다는 것만 알면 된다!
Child Task가 있다는 것은 Parent Task도 있다는 것!
예를들어 async let의 경우
fetchOneThunbnail이 Parent가 되게 된다.
이렇게 Task간의 명시적인 관계(Parent-Child)를 형성하기 때문에 이를 조직화할 수 있으며, 이러한 특성 때문에 Structured Concurrency라고 불린다.
(Task Tree에 대한 자세한 설명은 다음에..)
---
Structured Task는 async let이나 TaskGroup으로 만들어진 Task(작업의 단위)를 의미하며
Structured Concurrency는 이러한 Structured Task를 조직화하는 방법론이라고 생각하면 될 것 같다.
+ WWDC나 Swift 공식문서에서 (Un)Structured Task, (Un)Structured Concurrency가 같이 나오는데, 그냥 한번 분리해서 정리해봤따..
물론 async let, TaskGroup중 하나만 선택해서 써야하는 건 아니고,
필요에 따라 TaskGroup안에서 async let Task를 호출하는 등(혹은 그 반대) 조합해서 사용할 수 있다,
[Structured Concurrency가 왜 좋을까?]
- 명시적인 관계(Parent-Child) 를 통해 취소 전파(propagating cancellation)를 쉽게 처리할 수 있음
- 직접 or 간접적으로 생성된 모든 Structured Child Task가 완료되어야만 Parent Task가 완료될 수 있음.
- Task간의 상호작용을 쉽게 추적하고 관리가 가능
# Unstructured Concurrency
[Swift에서 Unstructured Task를 만드는 방법]
1. Task.init
2. Task.detached
[Unstructured Task? Unstructured Concurrency?]
Structured Concurrency가 Task간 명확한 관계를 형성하기 때문에 구조적이었다면,
Unstructured Concurrency는 Task간 명확한 관계가 없기때문에 '구조화되지 않았다'고 볼 수 있다.
Child Task가 만들어지던 Structured Task와는 달리
Task간 실행순서나 상호작용이 서로 독립적이며 상호작용이 자유로워 유연성은 뛰어나지만, 추적이 어렵고 취소 전파가 자동으로 되지 않는 등 관리가 어려울 수 있다.
[언제 Unstructured Task를 만들어야 할까?]
좋은점(?)은 바로 위에서 설명했으니 언제 만들어야하는지 보자.
다들 이미 잘 쓰고 있을 것 같은데, non-async 컨텍스트에서 Task를 실행하고 싶을 때 유용하게 사용할 수 있다.
@MainActor
class MyDelegate: UICollectionViewDelegate {
func collectionView(_ view: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt item: IndexPath) {
let ids = getThumbnailIDs(for: item)
let thumbnails = await fetchThumbnails(for: ids)
display(thumbnails, in: cell)
}
}
예를 들어 위와같이 UICollectionViewDelegate 메소드는 async가 아니기 때문에
await fetchThumnails를 사용할 수 없다. 이럴 때
@MainActor
class MyDelegate: UICollectionViewDelegate {
func collectionView(_ view: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt item: IndexPath) {
let ids = getThumbnailIDs(for: item)
Task {
let thumbnails = await fetchThumbnails(for: ids)
display(thumbnails, in: cell)
}
}
}
Task.init을 사용하여 비동기 작업을 수행할 수 있다.
[언제 어느것을 써야할까?]
정답은 없고, 적재적소에 맞는 Concurrency 모델을 사용해야겠다.
하지만 가능하면 Structured Concurrency를 선택하는 것이 좋다. (Beyond the basics of structured concurrency 에서도 그렇게 말하고있음)
[참고]
https://developer.apple.com/documentation/swift/task
https://developer.apple.com/wwdc21/10134
https://developer.apple.com/wwdc23/10170
https://docs.swift.org/swift-book/documentation/the-swift-programming-language/concurrency/