Swift/Concurrency

Structured Concurrency / Unstructured Concurrency

Zedd0202 2023. 7. 23. 18:54
반응형

 

Concurrency는 뭔가 아~~하고 나서 또 안보면 까먹는 그런 친구같네.. 

간단한게만 살펴보자~~

 

# Task

비동기 작업 단위 (A unit of asynchronous work)

모든 비동기(asynchronous) 코드는 어떠한 Task의 일부로 실행된다.

 

# Structured Concurrency / Unstructured Concurrency

우선 결론!! 이미지로 간단하게 보자면 아래와 같다. 

https://developer.apple.com/wwdc23/10170

 

# 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의 경우

https://developer.apple.com/wwdc21/10134

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/

반응형