티스토리 뷰
안녕하세요 다시 돌고 돌아 티스토리로 왔어요 ㅋ
이유는.. 뭐랄까.. 티스토리가 가장 함께 하는 느낌이 강하다고 해야하나.. 벨로그가 이뿌긴 한데 이웃도 할 수 있고 한 점이 티스토리가 월등히 낫네요.. 이렇게 쓰고 또 언제 벨로그로 갈지 몰겟음
Coroutine → 함수가 동작하는 도중 suspend, resume 가능
선언에 async가 붙으면, 중간에 suspend할 수 있음을 나타낸다. 그리고 suspend할 수 있는 특정시점은 함수 내의 awiat이다. await는 결국 중단점을 나타내고, async는 선언부에서 멈출 수 있다는것을 나타내는 것임
await가 붙은 함수가 실행되고 난후, 완료되면 (결과값이 나오거나 오류가 나오면) 다시 resume할 수 있다.
Asynchronous code는 프로그램이 실행되는 어느 한 지점에 suspend, resume할 수 있도록 한다. 이런 과정들은 예를들어 서버에서 데이터를 가져와서 UI를 업데이트 하는 등 시간이 걸리는 작업을 할 때 사용된다. 4코어 컴퓨터가 동시에 4개의 일을 할 수 있는 이런 것을 Parallel Code라고 하는데 프로그램이 이런 비동기나 Parallel 코드들을 사용해서 여러 작업을 한번에 수행할 수 있다. 이것들은 memory-safe한 방법으로 코드를 쉽게 쓸 수 있게 해주는데, 좀.. 복잡해진다. Swift Concurrency를 사용하면 컴파일 타임에 체크할 수 있다. Async함수는 스레드 제어권을 포기하고, 다른 작업을 실행할 수 있도록 한다. 함수가 재개될 때는 원래 실행하던 스레드에서 계속 실행할지는 모른다.
listPhotos(inGallery: "Summer Vacation") { photoNames in
let sortedNames = photoNames.sorted()
let name = sortedNames[0]
downloadPhoto(named: name) { photo in
show(photo)
}
}
다운로드하고 사진 보여주는 건데 읽기 어렵다. completion handler가 많아지면 참 복잡해진다..
결국 Asynchronous함수와 메서드는 실행중 중단될 수 있는 것을 나타내고, 실행을 완료하거나 에러를 던지거나 아무것도 리턴하지 않는.. 모든 것을 할 수 있지만 중단되어 무언가를 기다릴 수도 있다. aysnc함수 내에는 그럼 어디에서 멈추게 될 수 있는지를 표시해주는 것이 필요하다. (에러 throw될 수 있는 곳에 try붙이는거랑 같음)
func listPhotos(inGallery name: String) **async** -> [String] {
let result = // ... some asynchronous networking code ...
return result
}
//async throws
let photoNames = **await** listPhotos(inGallery: "Summer Vacation") // suspension point
let sortedNames = photoNames.sorted()
let name = sortedNames[0]
let photo = **await** downloadPhoto(named: name) // suspension point
show(photo)
코드가 실행되는데 await(중단점)에서 실행을 멈추고(시간이 걸리는 작업임), listPhotos()가 리턴될때까지 기다린다. 코드 실행이 멈추면, 프로그램 내의 다른 concurrent code가 실행된다. 그 concurrent 코드도 실행되다가 await를 만나거나 완료가 될때까지 실행되겠지.. 그리고 드디어 listPhotos가 리턴되면 이제 다시 코드가 실행된다. 리턴된 값을 photoNames에 할당한다. 그리고 sortedName 부분은 await도 없고 그냥 동기 코드니까 딱히 중단 없이 그대로 실행된다. 그러다가 downloadPhoto()를 만나면 다시 코드가 멈춘다. 그리고 또다시 같은 프로그램 내의 다른 concurrent 코드가 실행되게 한다. 값이 리턴되면 이제 .. photo에 할당하고 드디어 사진을 모여주게 된다.
결국 코드에 await가 있다는 것은 코드가 값을 받아올 때까지 중단될 수 있다는 것을 의미한다. 그리고 이는Yeildiing Thread로 볼 수 있다. Yeilding(양보) Thread는 그럼 뭐냐.. 현재 실행 대기중인 동등한 우선 순위 이상의 다른 스레드에게 실행 기회를 양보하는 것이다. Swift 비동기 함수의 경우 중간에 suspend를 하고 이를 실행하고 있던 스레드가 다른 일을 할 수 있도록 하는거니까 양보하는게 맞다.
💡 In computer science, yield is an action that occurs in a computer program during multithreading, of forcing a processor to relinquish control of the current running thread, and sending it to the end of the running queue, of the same scheduling priority
그리고 await는 결국 중단될 수 있다는 거니까 특정한 조건에서만 쓸 수 있다. async 함수, 프로퍼티나 @main이라고 명시된 구조체, 클래스, 열거형의 static main().. 그리고 unstructured child task..
//Task.sleep -> 기다리기
func listPhotos(inGallery name: String) async throws -> [String] {
try await Task.sleep(until: .now + .seconds(2), clock: .continuous)
return ["IMG001", "IMG99", "IMG0404"]
}
여기서 listPhotos는 한번에 3가지를 리스트 형태로 리턴하고 있는데, 하나씩 받아온다면 어떨까? 이에 대한 이야기가 asynchronous sequence에 관한 것이다.
import Foundation
let handle = FileHandle.standardInput
for try await line in handle.bytes.lines {
print(line)
}
for에 중단 될 수 있는 지점을 나타내주는 await를 사용했다. for await - in을 사용하면 반복문 중간에 실행이 중단될 수 있다는 것을 말해준다. for - in 루프에서 Sequence프로토콜을 준수하는 것을 사용하는 것과 마찬가지로 for await - in 루프에서는 AsyncSequece 프로토콜을 준수하는 것을 사용할 수 있다.
let firstPhoto = await downloadPhoto(named: photoNames[0])
let secondPhoto = await downloadPhoto(named: photoNames[1])
let thirdPhoto = await downloadPhoto(named: photoNames[2])
let photos = [firstPhoto, secondPhoto, thirdPhoto]
show(photos)
이 코드는 괜찮아 보일수도 있지만 좀 문제가 있는데 await가 중단점이라고 생각하면 downloadPhoto가 세개가 하나씩.. 하나씩.. 하나가 완료되면 또 실행되고 그게 완료되면 또 실행되고.. 이렇게 된다. 사실 downloadPhoto는 순차적으로 실행될 필요가 전혀없다. 각각 서로한테 영향을 안주는 작업이기 때문에 그냥 동시에 실행해도 된다. let 앞에 async를 적어주고, 사용시점에 await를 쓰면 된다.
**async let** firstPhoto = downloadPhoto(named: photoNames[0])
async let secondPhoto = downloadPhoto(named: photoNames[1])
async let thirdPhoto = downloadPhoto(named: photoNames[2])
let photos = await [firstPhoto, secondPhoto, thirdPhoto]
show(photos)
- async func + await
- async func + async let: 결과를 바로 얻을 필요가 없을 때, 병렬적으로 동시에 실행해도 괜찮을 때
- await + async let
그럼 Task는 뭔데..
Task | Apple Developer Documentation
Task는 비동기적으로 실행될 수 있는 작업의 단위를 말한다. 결국 모든 비동기 코드는 task의 부분인것이고.. async-let 구문도 child task를 만드는 것이다. Task는 생성과 동시에 실행된다. Task를 만들고 나면 이와 상호작용 하기 위해서 인스턴스를 사용할 수 있다. 이 인스턴스를 이용해서 완료를 기다리거나.. 취소할 수 있다. task는 reference가 있거나 없거나.. 실행되는데 만약 reference를 없앤다고 하면 딱히 task의 결과를 받아온다거나.. task를 취소한다거나 할 순 없을 것이다 (당연)
task를 만들고, child task를 추가할 수 있다. Task는 계층 구조를 갖는데, 부모 task와 child task가 있다. 이러한 접근을 structured concurrency라 한다.
await **withTaskGroup**(of: Data.self) { taskGroup in
let photoNames = await listPhotos(inGallery: "Summer Vacation")
for name in photoNames {
**taskGroup.addTask** { await downloadPhoto(named: name) }
}
}
그럼 Unstructured Concurrency는 뭐임..
Unstructured Concurrency는 parent task가 없다. current actor 일 경우 Task.init(priority:operation:) 으로, current actor가 아니면 Task.detached(priority: operation)으로 만들면 된다.
let newPhoto = // ... some photo data ...
let handle = Task {
return await add(newPhoto, toGalleryNamed: "Spring Adventures")
}
let result = await handle.value
Task는 적절한 시점에 Cancel될 수도 있다. cancellation을 확인하기 위해 Task.checkCancellation()을 확인할 수 있다. (캔슬되었으면 CancellationError예외를 발생시킨다.) 또는 Task.isCancelled로 확인할 수도 있다.
'iOS' 카테고리의 다른 글
[Swift Concurrency] Actor - Actor모델 (행위자 모델) (2) | 2023.07.03 |
---|---|
[Swift Concurrency] 기존 GCD 방식과 Swift Concurrency의 차이점이 무엇일까? + Continuation? (2) | 2023.07.02 |
[WWDC] Understanding Swift Performance (1) - Allocation (1) | 2022.02.13 |
[iOS/Swift] unrecognized selector sent to instance .. (4) | 2021.06.24 |
[iOS/Swift] UIView, UIImageView에 그림자주기 (0) | 2021.06.07 |