Task 개념
비동기 코드를 실행하기 위한 새로운 실행 컨텍스트를 제공함 async-await를 호출할 수 있게 하고, 병렬로 코드 실행을 지원하는 매커니즘
우린 여태껏 async 호출을 통해 비동기 작업을 다뤘다.
그러나 async를 사용한다고 해서 한 번에 둘 이상의 작업을 수행하지 않는다.
async가 동시성을 제공하는 건 아니라는 뜻
Task는 코드를 병렬로 실행하게 하며 각 Task는 다른 Task와 동시에 실행할 수 있는 비동기 컨텍스트를 제공한다.
Task로 생성된 작업은 MainActor가 아니라면 백그라운드 스레드에서 실행된다. await를 사용해서 완료된 값이 돌아올 때까지 기다릴 수 있음
하나의 Task 안에서 async 함수를 호출하고,
그 안에서 또 async를 호출한다면 동일한 Task에서 실행된다.
우선순위
high, default, low, background 의 우선순위를 제공한다.
Task를 호출할 때 Task { }가 아닌, Task(priority: .high) { }를 통해 지정이 가능하다.
Task 종류
async let
동시 바인딩을 지원하고자 나온 Task의 한 종류
let result = try await URLSession.shared.data()
기본적으로 URLSession의 data 비동기 메소드가 실행되고, 메소드의 작업이 완료됐을 때 result라는 L Value에 값이 초기화 된다.
이것은 하나의 흐름으로 문제 없음
그러나, let 앞에 async let을 사용하여 동시 바인딩을 제공함
async let result = URLSession.shared.data
동시 바인딩을 위해 Swift는 새로운 Child Task를 생성한다.
모든 Task는 프로그램의 실행 컨텍스트를 나타내기에 이때 두 개의 화살표가 동시에 나타남
첫 번째: 데이터 다운로드를 즉시 시작하는 하위 작업을 위한 것
두 번째: 부모 작업에 대한 것, 즉시 변수 결과를 Assign Placeholder, 바인딩 함 (그러나 첫 번째 작업이 끝나기 전까지는 비어있음)
요 두 번째가 잘 이해 안되긴 하는데«, 뒷 내용 보면 대충 변수 값만 비어있고 변수의 형태를 띄게한다..? 이느낌으로 이해함
하위 항목이 데이터를 동시에 다운로드하는 동안, 상위 작업은 동시 바인딩을 따르는 문을 계속 실행한다고 함
그러나, result의 실제 값이 필요한 순간이 오면, 이때부터 URLSession.shared.data()의 작업이 끝나기를 기다림
이렇게 해서 동시에 바인딩을 한다, 이 느낌..?
이론은 이런데 일단 아래 코드보면서 이해 ㄱㄱ
async-let 예제
두 가지 다른 URL로부터 데이터를 다운로드하는 예제가 있다.
현재 코드는 순차적 바인딩이다.
하나는 이미지를 받는 거고, 하나는 이미지에 대한 메타 데이터용.
이러면 imageReq를 통해 이미지를 받아올 때까지 기다리고,
그 후에 metadata를 받아올 때까지 기다려서 이미지를 만들고 반환을 하게 된다.
그리고 오류 가능성이 있기 때문에 try await 을 사용해서 호출해야 한다.
async-let 도입
두 다운로드가 동시에 이루어질 수 있게 async-let을 사용하여 동시 바인딩을 한다.
이러면 Child Task에서 작업이 발생하기 때문에 try await을 사용하지 않아도 됨
이제 아래 블록들에서 data와 metadata 변수를 사용하기 전에 try await을 한다.
한 비동기 메소드에서 다른 비동기를 호출할 때마다 동일한 Task를 사용해서 호출한다.
fetch~ 메소드에서 두 개의 async-let으로 두 개의 자식 Task를 만든 것.
이러니까 트리 구조가 되는 거고, 상위에서는 await를 할 필요 없이 작업이 완료된 경우에 만 부모가 작업을 완료할 수 있다고 말함
(9분 21초)
Task Group
새로운 Task가 기존 Task에 종속된 관계를 갖게 할 수 없을까?
그래서 Task Group이 필요하다.
Task {
let arr: [Int] = await withTaskGroup(of: Int.self) { group in
var arr = [Int]()
group.addTask {
1
}
group.addTask {
1
}
for await int in group {
arr.append(int)
}
//-----
// while let int = await group.next() {
//
// }
//-----
// var it = group.makeAsyncIterator()
// while let int = await it.next() {
//
// }
return arr
}
}
TaskGroup은 비동기 함수라서 비동기 맥락 안에서 실행되어야 한다.
또한 2가지 정보가 필요하다. (자식 작업의 return 타입, 그룹 작업의 return 타입)
그룹 작업의 return 타입의 경우 대개 타입추론으로 해결해주는데 필요한 경우 직접 적어야 한다.
이렇게 하면 비슷한 작업에 대해 자식 작업을 만들어서 동시에 여러 자료를 취합할 수 있게 된다.
이 방식을 구조적 동시성이라고 한다.
- 계층적으로 부모 - 자식 관계를 형성하고, 부모는 자식작업이 끝날때까지 기다린다.
- 작업의 우선순위 = max(부모, 자식)
TaskGroup에는 Throwing할 수 있는 ThrowingTaskGroup이 별도로 있다.
자식작업을 많이 만들어서 일을 더 잘게 분해하는 게 좋은 것같지만 꼭 그렇지는 않다.
Task는 자식작업이 모두 완료되는 것을 기다리기 때문에 해당 부모작업의 종료시까지 자식작업들의 메모리를 들고 있게 된다.
그 결과를 사용하기 위해서라면 필요하지만 경우에 따라 자식 작업의 결과가 중요하지 않을 수도 있다.
이 경우 with(Throwing)DiscardingTaskGroup()을 사용하면 된다.
이것을 사용하면 자식 작업은 반환되자마자 메모리에서 해제된다.
협력적 취소
말그대로 취소에 “협력”한다는 것이다.
우리가 Task에 대해 취소 명령을 날리면 그 즉시 취소(함수 return)되는 것이 아니다.
다만, Task는 최대한 빨리 취소가 될 수 있도록 “협력”하는 것이다.
이렇게 하는 이유는 Task와 그 하위 작업들에게 취소가 되었을 경우 어떻게 할 것인지 여지를 주기 위해서다.
만약 바로 함수를 종료시키면 취소가 되었을 경우 어떻게 해야하는지를 가이드할 수 없다.
그러나 Task가 종료되었을 경우에 어떤 행동을 취할지 개발자에게 여지를 줌으로써 더 유연한 개발이 가능하다.
func fetchThumbnails(for ids: [String]) async throws -> [String: UIImage] {
var thumbnails: [String: UIImage] = [:]
for id in ids {
try Task.checkCancellation()
// if Task.isCancelled { break }
thumbnails[id] = try await fetchOneThumbnail(withID: id)
}
return thumbnails
}
비동기 함수를 작성할 때 Task의 타입 메서드로 checkCancellation()과 타입계산속성 isCancelled를 사용할 수 있다.
- checkCancellation(): 현 작업이 취소되었는지 확인한 후 취소일 경우 에러를 던짐
- isCancelled: 현 작업이 취소되었으면 true, 아니면 false 반환
이를 통해 보통 긴 작업을 시작하기 전에 적절히 작업 취소 여부를 확인하여 코드를 작성하면 된다.
이러한 방식을 하나도 구현 안하면 작업이 취소되어도 자신의 작업을 계속한다.
그러기 때문에 협력적 취소라고 부르는 것이기도 하다. (비협조적이면 취소가 안된다…)
위의 Task 타입메서드/속성을 사용하면 현재 자신의 Task를 자동으로 추적한다.
협력적 취소는 구조적 동시성에서 유용하다!
- 자식 작업이 오류등을 날리면 다른 작업들도 멈추게 된다.
Explore structured concurrency in Swift - WWDC21 - Videos - Apple Developer
When you have code that needs to run at the same time as other code, it's important to choose the right tool for the job. We'll take you...
developer.apple.com
https://developer.apple.com/videos/play/wwdc2022/110351
Eliminate data races using Swift Concurrency - WWDC22 - Videos - Apple Developer
Join us as we explore one of the core concepts in Swift concurrency: isolation of tasks and actors. We'll take you through Swift's...
developer.apple.com
'📱 iOS > Swift Concurrency' 카테고리의 다른 글
[Swift Concurrency 10편] Actor는 한글 키보드로 'ㅁㅊ색' 이란 걸 아시나요? (0) | 2025.02.26 |
---|---|
[Swift Concurrency 9편] Sendable 프로토콜 (0) | 2025.02.26 |
[Swift Concurrency 7편] 비동기 호출에서의 스레드 제어권 (0) | 2025.02.26 |
[Swift Concurrency 6편] Swift Concurrency 등장 배경 (1) | 2025.02.26 |
[Swift Concurrency 5편] 동시성 프로그래밍 with GCD (0) | 2025.02.25 |