<br/>
- -> **그래서 swift에서 제공하게된 “구조화된 동시성 (Structured Concurrency)”**
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
# async-await
- swift에서 “구조화된 동시성 (Structured Concurrency)” 제공 방법? **async-await**
- async 키워드를 사용해서 비동기적으로 동작하는 함수를 만든다
- 이 때, 이 비동기 함수는 코루틴(`Coroutine`)으로 만들어지는데
> << `Coroutine` >>
Swift 언어에서의 `Coroutine`은 비동기적인 작업을 간편하게 다룰 수 있도록 해주는 기능입니다. Swift의 `Coroutine`은 Swift 5.5부터 async/await 패턴을 도입하여 사용할 수 있게 되었습니다. 이를 통해 비동기적인 작업을 동기적인 코드와 유사하게 작성할 수 있으며, 가독성과 유지보수성이 향상됩니다.
swift의 async-await와 같은 동작이 Kotlin에서 동시성프로그래밍에서는 `Coroutine`이라는 이름으로 불린다.
CPS(Continuation Passing Style) → Swift에서는 async-await, Kotlin에서는 coroutine
- `Coroutine`은 함수가 동작하는 도중 특정시점에 일시정지(suspend)할 수 있고 resume(다시재개)할 수 있게한다. → 비동기함수가 일시정지(suspend)될 수 있는 지점을 알려주는 장치가 await이다.
- 함수의 이름 뒤에 async 키워드를 붙여 해당 함수가 비동기 함수임을 나타낼 수 있고, await 키워드로 비동기함수가 일시정지되는 지점을 알려준다.
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
# `Coroutine`으로 만들어지는 async 비동기 함수
→ 이함수는 비동기적으로 동작할 수 있고 await 키워드를 사용해 비동기 함수의 결과를 대기할 수 있습니다. ( 비동기 함수가 일시정지될 수 있는 지점을 알려주는 장치가 await)
```swift
func makeCrunchyCookie() async throws -> Cookie {
let dough = try await makeDough() // ✅ (1)
let ripedDough = try await chillDough(dough: dough) // ✅ (2)
let cookie = try await bakeCookie(dough: ripedDough, time: bakeTime) // ✅ (3)
let crunchyCookie = try await drawFace(cookie: cookie) // ✅ (4)
return crunchyCookie
}
async로 만들어진 비동기 함수는 코루틴(Coroutine)으로 만들어진다 ( Coroutine은 CPS(Continuation Passing Style) 에서 나온 개념이므로 CPS(Continuation Passing Style) 즉, 일시 중지→ 작업재개 되는 과정에서 연속성(Continuation)을 가지고 작동할 수 있다는 것)
→ 이렇게 코루틴(Coroutine) 만들어진 비동기 함수는 결과를 반환할 때까지 일시정지(suspend)될 수 있는데,
→ 비동기 함수가 일시정지될 수 있는 지점을 알려주는 장치가 await 키워드이다 (suspension point)
→ await키워드로 호출된 함수가 결과값 혹은 오류를 던진 후에 resume(실행재개)할 수 있다.
비동기함수의 suspend → resume 과정
스레드 제어권을 시스템에 넘겼다가 다시 받음
비동기 함수 실행 중 await만나면 suspension point로 지정하고 일시정지(suspend)
→ 시스템에게 스레드 제어권 넘겨줌
→ 다시 task 수행이 필요하다고 느껴질 때 비동기함수에게 스레드 제어권 다시 넘겨줌
→ suspension point 에서 작업 재개
비동기함수를 처리할 때 suspend/resume
비동기함수는 여러 task의 집합으로 이루어져있는데, ( 비동기함수 내에서 다른 비동기 함수를 호출하는 것도 해당 호출또한 task)
Swift의 continuation(연속성)
continuation(연속성)은 프로그래밍 언어 스위프트의 기능 중 하나. 이는 어떤 작업이나 프로세스를 계속해서 실행하거나 유지할 수 있도록 하는 것을 의미한다.
예를 들어, 스위프트에서의 연속성은 비동기 작업을 처리하거나 이벤트 기반 프로그래밍에서 사용될 수 있습니다.
스위프트에서 continuation(연속성)을 구현하는 방법에는 클로저, 비동기 함수 및 스레드 등이 포함될 수 있습니다. 이러한 기능은 주로 앱의 성능을 향상시키고 사용자 경험을 개선하기 위해 사용됩니다.
continuation은 task가 suspend되었을 때(ex. 비동기 함수에서 await 구간) 발생하며, resume되었을 때 이 continuation 을 사용해서 suspension point로 다시 돌아갈 수 있다. (일시정지 포인트가 있음에도 불구하고 연속되게 처리될 수 있다는 것)
이 각 task들은 각각 어떤 스레드에서 동작할지 executor을 통해 결정한다. (함수가 suspend되는 과정에서도 executor가 추가적인 스레드 생성 없이 작업할 수 있도록 적절한 스레드에 배정한다)→ but, task가 resume될 때 이전에 진행했던 스레드와 같지 않을 수 있다 )
executor : 스레드 관리를 개발자가 아닌 시스템 자체에서 함
비동기 작업을 할 때 suspend→ resume 과정에서 애플이 스레드 관리를 시스템 자체에서 할 수 있도록 하는 것(이라고 이해했다)
추가적인 스레드 없이 작업할 수 있도록 task를 적절한 스레드에 배정하는 과정이 포함되어 잇고 다시 원래 작업으로 돌아갈 때 task에게 제어권을 넘겨준다
여기서 task가 재개되는 스레드는 이전에 진했됐던 스레드와 같지 않을 수 있다는 점 알아두기
suspension point에서 변할 수 있는 것 세 가지!!
스레드 제어권
resume되는 스레드
suspend → resume 이후 앱의 상태
Coroutine - async로 만들어진 비동기 함수는 코루틴(Coroutine)으로 만들어짐. 비동기 작업 & 동시성 프로그래밍을 가능하도록하는 것. 이렇게 만들어진 비동기 함수는 suspend 가능
continuation - 비동기 작업에서 suspend-> resume을 가능하도록하는 것
executor - 비동기 작업을 할 때 suspend→ resume 과정에서 애플이 스레드 관리를 시스템 자체에서 할 수 있도록 하는 것
✅ GCD & completion handler의 문제점과 Swift Concurrency 의 도입 이유!
기존 비동기 처리의 문제점
중첩되는 들여쓰기로인한 가독성 저하
개발자의 실수로 인해 발생할 수 있는 오류
클로저에서 self 캡처로 인해 순환 참조가 발생할 가능성
복잡한 에러핸들링
```swift
// (2a) Using a `guard` statement for each callback:
func processImageData2a(completionBlock: (_ result: Image?, _ error: Error?) -> Void) {
loadWebResource("dataprofile.txt") { dataResource, error in
guard let dataResource = dataResource else {
completionBlock(nil, error)
return
}
loadWebResource("imagedata.dat") { imageResource, error in
guard let imageResource = imageResource else {
completionBlock(nil, error)
return
}
decodeImage(dataResource, imageResource) { imageTmp, error in
guard let imageTmp = imageTmp else {
completionBlock(nil, error)
return
}
dewarpAndCleanupImage(imageTmp) { imageResult, error in
guard let imageResult = imageResult else {
completionBlock(nil, error)
return
}
completionBlock(imageResult)
}
}
}
}
}
processImageData2a { image, error in
guard let image = image else {
display("No image today", error)
return
}
display(image)
}
```
```swift
// (2b) Using a `do-catch` statement for each callback:
func processImageData2b(completionBlock: (Result) -> Void) {
loadWebResource("dataprofile.txt") { dataResourceResult in
do {
let dataResource = try dataResourceResult.get()
loadWebResource("imagedata.dat") { imageResourceResult in
do {
let imageResource = try imageResourceResult.get()
decodeImage(dataResource, imageResource) { imageTmpResult in
do {
let imageTmp = try imageTmpResult.get()
dewarpAndCleanupImage(imageTmp) { imageResult in
completionBlock(imageResult)
}
} catch {
completionBlock(.failure(error))
}
}
} catch {
completionBlock(.failure(error))
}
}
} catch {
completionBlock(.failure(error))
}
}
}
processImageData2b { result in
do {
let image = try result.get()
display(image)
} catch {
display("No image today", error)
}
}
```
> Result 를 써줬다고 하더라도 completionBlock 에 대한 호출을 누락할 수 있다는 허점은 여전.
> 문법적인 패턴을 도입해 보다 명확하고 안전하게 문제를 해결하고자함
➡️
> Swift Concurrency (async-await)
# ✅ async-await와 Coroutine
- async 키워드를 사용해서 비동기적으로 동작하는 함수를 만들 수 있는데
- 이 때 비동기 함수는 **Coroutine**으로 만들어진다.
- **Coroutine**이란?
- → 함수가 동작하는 도중 특정 지점에서 suspend(일시정지)할 수 있고 resume(재개)할 수 있다.
- → 이 때 비동기 함수가 일시정지(suspend) 될 수 있는 지점을 알려주는 장치가 **await**키워드이다
>
```swift
func loadWebResource(_ path: String) async throws -> Resource
func decodeImage(_ r1: Resource, _ r2: Resource) async throws -> Image
func dewarpAndCleanupImage(_ i : Image) async throws -> Image
func processImageData() async throws -> Image {
let dataResource = try await loadWebResource("dataprofile.txt")
let imageResource = try await loadWebResource("imagedata.dat")
let imageTmp = try await decodeImage(dataResource, imageResource)
let imageResult = try await dewarpAndCleanupImage(imageTmp)
return imageResult
}
```
> ⭐️ async/await 비동기 함수를 사용하면 비동기 코드를 마치 동기 코드인 것처럼 작성할 수 있다.
# ✅ sync-await에서의 Continuation이란?
- 함수 컨텍스트를 "추적"하는 객체
- await를 사용해서 일시정지(suspend)할 수 있는 비동기 함수에서 suspend→ resume 과정에 필요!
- async-await에서는 async로 만들어진 비동기 함수 내부의 각task에 함수 컨텍스트를 추적하는 continuation가 할당된다.
- → continuation은 task가 suspend되었을 때 발생
- → continuation 덕분에 task에 다시 스레드 제어권 넘겨서 resume될 때 이를 이용해서 suspension point로 돌아갈 수 있다.
> (*스레드 제어권과 suspend-> resume 에 관련해서는 다음 글에 자세히! )
# ✅ GCD와 Swift Concurrency 의 스레드 생성
## GCD를 사용할 때는 비동기 task마다 스레드를 생성
> 📍아직 확실하게는 확인 못한 부분
- task하나 suspend 하고 다른 작업 넘어가면
- -> 스레드 +1 생성
## 그렇다면 과연 스레드 수가 많아지면 더 좋은걸까?
no !!디바이스의 cpu 코어수보다 많아지면 실제 용량을 초과해 요구하는 셈이 된다(overcommit)
- → 실행중이었던 스레드 block( block된 스레드가 다시 실행도기를 기다리면서 가지고 있는 메모리 및 리소스때문에 `메모리 오버헤드` 가능성)
- → 스레드가 과도하게 생성돌 경우 빈번한 컨텍스트 스위칭 으로 인한 과도한 `컨텍스트 스위칭`(`스케줄링 오버헤드` 가능성)
## Swift Concurrency (async-await 문법) 에서는 스레드 관리를 시스템 자체에서 처리해 안전성을 보장하고자한다 (executor개념 등장)
- 어떤 비동기 task가 일시정지(suspend)되면 ⭐️ 시스템에게 스레드 제어권 넘겨줌 ⭐️
- -> 시스템이 넘겨받은 작업의 우선순위와 실행하기에 적절한 스레드를 고려하여 task가 resume될 때 스레드 제어권을 다시 넘겨줌
- 함수가 suspend되는 과정에서 추가적인 스레드의 생성 없이도 작업할 수 있도록
- **executor**가 task를 적절한 스레드로 배정하는 과정이 포함되어 있다.
- executor : 비동기 작업을 할 때 suspend→ resume 과정에서 애플이 스레드 관리를 시스템 자체에서 할 수 있도록 하는 것
> 여기에 executor에 대한 자세한 내용 나오는 것같은데 너무 생소하고 어려워서 다음에 다시 파보기로..
https://github.com/apple/swift-evolution/blob/main/proposals/0392-custom-actor-executors.md#introduction
비동기 작업 기존 방법
func makeCrunchyCookie(completion: @escaping ((Cookie) -> Void)) { makeDough { dough in // ✅ (1) self.chillDough(dough: dough) { ripedDough in // ✅ (2) self.bakeCookie(dough: ripedDough, time: bakeTime) { cookie in // ✅ (3) self.drawFace(cookie: cookie) { crunchyCookie in // ✅ (4) completion(crunchyCookie) } } } } }
Coroutine
)으로 만들어진다 (Coroutine
은 CPS(Continuation Passing Style) 에서 나온 개념이므로 CPS(Continuation Passing Style) 즉, 일시 중지→ 작업재개 되는 과정에서 연속성(Continuation)을 가지고 작동할 수 있다는 것)비동기함수의 suspend → resume 과정
스레드 제어권을 시스템에 넘겼다가 다시 받음
비동기 함수 실행 중 await만나면 suspension point로 지정하고 일시정지(suspend)
→ 시스템에게 스레드 제어권 넘겨줌
→ 다시 task 수행이 필요하다고 느껴질 때 비동기함수에게 스레드 제어권 다시 넘겨줌
→ suspension point 에서 작업 재개
비동기함수를 처리할 때 suspend/resume
continuation
은 task가 suspend되었을 때(ex. 비동기 함수에서 await 구간) 발생하며, resume되었을 때 이 continuation 을 사용해서 suspension point로 다시 돌아갈 수 있다. (일시정지 포인트가 있음에도 불구하고 연속되게 처리될 수 있다는 것)executor : 스레드 관리를 개발자가 아닌 시스템 자체에서 함
suspension point에서 변할 수 있는 것 세 가지!!