일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
Tags
- 알고리즘
- deadlock
- 동시성
- 운영체제
- 데드락
- decode
- struct
- UserDefaults
- Algorithm
- scrollview
- Apple Developer Academy
- 비동기
- forEach
- Swift
- Codable
- 오브젝트
- 인프런
- Linked List
- 100 days of SwiftUI
- 프로세스 스케줄링
- core data
- @state
- async
- 가상 메모리
- IOS
- SwiftUI
- 동기화
- 앨런
- 상호배제
- COLOR
Archives
- Today
- Total
기어가더라도 제대로
[iOS-더 나아가기] withTaskGroup - 동시적으로 async 코드 블럭 실행 본문
Operation
struct SlowDivideOperation {
let name: String
let a: Double
let b: Double
let sleepDuration: UInt64
func execute() async -> Double {
do {
// Sleep for x seconds
try await Task.sleep(nanoseconds: sleepDuration * 1_000_000_000)
let value = a / b
return value
} catch {
return 0.0
}
}
}
let operations = [
SlowDivideOperation(name: "operation-0", a: 5, b: 1, sleepDuration: 5),
SlowDivideOperation(name: "operation-1", a: 14, b: 7, sleepDuration: 1),
SlowDivideOperation(name: "operation-2", a: 8, b: 2, sleepDuration: 3)
]
excute() async -> Double
: 간단한 나눗셈 작업을sleepDuration
초 동안 기다린 후에 완료operations
: 작업 구조체들의 배열입니다.- 이 작업들을 동기적(Serial)으로 실행할지 동시적(concurrency)으로 실행할지 간단한 실험을 하나 해보겠습니다.
- 순차적 작업 예상 소요 시간: 5 + 1 + 3 = 9초입니다.
- 동시적 작업 예상 소요시간: 가장 긴 작업이 걸리는 시간인 5초 입니다.
순차적 실행(Serial)
//MARK: - 동기적 코드 실행
Task {
print("Start Task!")
let start = Date.timeIntervalSinceReferenceDate
for operation in operations {
let value = await operation.execute()
print("\(operation) is Done!")
let middle = Date.timeIntervalSinceReferenceDate
print(String(format: "Duration: %.2fs", middle-start))
}
let end = Date.timeIntervalSinceReferenceDate
print("End Task!")
print(String(format: "Duration: %.2fs", end-start))
}
operation
이 완료될 때마다 시간이 출력되고 전체 작업이 모두 종료되면 End Task 이후에 모든 시간이 출력됩니다.- 작업 순서는 0번, 1번, 2번 순으로 완료가 되네요. 작업을 호출한 순서대로입니다.
- 총 소요시간은 9.59초
비동기적 실행(Concurrency)
//MARK: - 동시적 코드 실행
Task {
print("Start Task!")
let start = Date.timeIntervalSinceReferenceDate
let allResults = await withTaskGroup(of: (String, Double).self, // child task return type
returning: [String: Double].self, // group task return type
body: { taskGroup in
// Loop through operations array
for operation in operations {
// Add child task to task group
taskGroup.addTask {
// Execute slow operation
let value = await operation.execute()
print("\(operation) is Done!")
let middle = Date.timeIntervalSinceReferenceDate
print(String(format: "Duration: %.2fs", middle-start))
// Return child task result
return (operation.name, value)
}
}
// Collect results of all child task in a dictionary
var childTaskResults = [String: Double]()
for await result in taskGroup {
// Set operation name as key and operation result as value
childTaskResults[result.0] = result.1
}
// All child tasks finish running, thus task group result
return childTaskResults
})
let end = Date.timeIntervalSinceReferenceDate
print("End Task!")
print(String(format: "Duration: %.2fs", end-start))
}
withTaskGroup
이 돌면서 동시적으로 작업을 수행합니다.- 작업 완료는 1번,2번, 가장 오래걸리는 0번 순으로 되었습니다.
- 전체 완료 시간은 5.34초
withTaskGroup 사용법
개수가 변하는 자식 Task를 포함하는 범위를 동작시킵니다.(??)
Discussion
A group waits for all of its child tasks to complete or be canceled before it returns. After this function returns, the task group is always empty.
To collect the results of the group’s child tasks, you can use a
for
-await
-in
loop:모든 자식 Task가 작업 완료되기를(혹은 취소되기를) 기다리는 그룹입니다. 함수가 반환이 되면, TaskGroup은 항상 비어있습니다.
그룹의 자식 Task 의 작업 결과를 얻기 위해서,
for
-await
-in
반복문을 사용하세요.
- of 파라미터: 자식 Task 가 완료한 작업의 결과의 타입을 나타냅니다.
- returning 파라미터: 전체 그룹이 완료되었을 때 반환하는 타입을 나타냅니다.
- 모두 메타타입으로 적혀있기 때문에 실제로 작성할 땐
SomeType.self
로 작성해야합니다. - 우리가 작성할 것은 Body 안쪽의 내용입니다. 두가지 작업을 합니다.
- 모든 그룹의 자식 프로세스를 동시적으로 실행
- 자식 테스크가 실행을 완료한 결과를 수확(?)
async
키워드가 Body 안에서 한번 나오고, 전체의 리턴타입 전에도 한번 나오니까 await 는 최소 2번 나와야겠습니다.- 실제로는 3번 나옵니다.
- withTaskGroup 실행할 때 한번
- for 문 안에서 비동기적인 자식 Task를 실행하면서 한번
- Body안에서 결과를 얻을 때 한번
Task {
let allResults = await withTaskGroup(of: (String, Double).self
returning: [String: Double].self,
body: { taskGroup in
~~~
})
}
Task
안에서 실행이 되어야하고,await
키워드를 붙여줍니다.- of: 파라미터에는 자식 Task가 완료되었을 때 반환할 타입을 나타냅니다
- returning: withTaskGroup이 반환할 타입을 나타냅니다.
let allResults = await withTaskGroup(of: (String, Double).self,
returning: [String: Double].self,
body: { taskGroup in
for operation in operations {
taskGroup.addTask {
let value = await operation.execute()
return (operation.name, value)
}
}
var childTaskResults = [String: Double]()
for await result in taskGroup {
childTaskResults[result.0] = result.1
}
return childTaskResults
})
- 노란색 테두리는 자식 Task의 개별 반환 타입을 의미하며
- 빨간색 테두리는 withTaskGroup의 전체 반환 타입을 의미합니다.
- await는 3번 나오네요.
- 비동기적으로 작업을 수행하기 위해서 표시한 것이라는 점을 기억해야합니다.
- 동기적으로 실행하는 코드가 아닙니다.
빨간색 테두리와 동일한 타입으로 잡히는 것을 확인할 수 있습니다.
결론
순서가 중요하지 않고 전체 걸리는 시간이 중요하다면 withTaskGroup
으로 동시적으로 코드를 사용
출처
Comments