Concurrency (await)
2024. 4. 16. 19:30ㆍiOS
Concurrency (await)
- Swift는 구조화된 방식으로 비동기 및 병렬코드를 작성하기 위한것을 기본으로 제공합니다.
- 비동기 함수가 재개될 때, Swift는 해당 함수가 어떤 스레드에서 실행될지에 대해 어떠한 보장도 하지 않습니다.
비동기 함수 정의 및 호출
- 비동기 메서드는 실행 도중에 일시 중지될 수 있으며, 완료되거나 오류를 던지거나 반환될 때까지 대기합니다. 이는 일반적인 동기적 함수나 메서드와 다릅니다. 동기적 함수나 메서드는 완료되거나, 오류를 던지거나, 또는 결코 반환되지 않을 때까지 계속 실행됩니다. 비동기 메서드는 특정 작업을 수행하는 동안 다른 작업을 기다리면서 중간에 일시 정지될 수 있습니다.
- 메서드가 비동적인것을 나타내려면
throws
를 사용하여throw
하는 함수를 표시하는 방법과 비슷하게 선언에서 해당 매개 변수 뒤에async
키워드를 작성합니다. 함수의 값을 반환하는 경우 -> 앞에async
를 씁니다.
func listPhotos(inGallery name: String) async -> [String] {
let result =
return result
}
비동기적 함수에 throws
하기전에 async
를 작성하면 됩니다.
- 비동기 메서드를 호출하면 해당 메서드가 반환될 때까지 실행이 일시 중지 됩니다. 호출앞에
await
를 작성하여 가능한 일시 중단 지점을 표시합니다.- 오류가 있는 경우 함수 흐름에 대한 가능한 변경 사항을 표시하기 위해
throw
를 호출 할때try
를 붙히는것과 같습니다. - 비동기 메서드 내에서 실행 흐름은 다른 비동기 메서드를 호출할 때만 일시 중지되며 가능한 모든 일시 중지 지점은
await
로 표시합니다.
- 오류가 있는 경우 함수 흐름에 대한 가능한 변경 사항을 표시하기 위해
- 반환 화살표 앞에
async
를 작성하여 둘다 비동기로 만들면 이 코드가 그림이 준비될 때 까지 기다리는 동안 앱의 나머지 코드가 계속 실행될 수 있습니다. - Swift에서 async를 사용하여 listPhotos(inGallery:)와 downloadPhoto(named:) 두 함수를 비동기로 만들면, 이 코드가 이미지를 준비하는 동안 앱의 다른 코드가 계속 실행될 수 있습니다. 이는 코드 실행이 블로킹되지 않는다는 것을 의미합니다."
let photoNames = await listPhotos(inGallery: "Summer Vacation") let sortedNames = photoNames.sorted() let name = sortedNames[0] let photo = await downloadPhoto(named: name) show(photo)
- 코드 순서
- 첫번째 코드가 첫줄부터 실행되어 처음 await에 도달할때까지 실행됩니다. 이때 listPhotos(inGallery:)를 호출하고 반환되기를 기다리는 동안 실행을 일시 중단합니다.
- 코드 실행이 일시 중단되는 동안 동일한 프로그램의 다른 동시 코드가 실행됩니다.
- listPhotos(isGallery:)가 반환되면, 이 코드는 해당 시점부터 실행 하고, 반환된 photoNames에 값을 할당합니다.
- sortedNames 및 name을 정의하는 줄은 일반 동기 코드입니다. await 표시가 없기에 일시 중지는 없다
- await donwloadPhoto함수에 표시 -> 이 함수가 반한될때까지 실행을 일시 중지하고 동시에 다른 코드를 실행할 수 있는 기회를 제공
- donwloadPhoto가 반환된 후 반환값이 photo에 할당된 다음 show(_:)를 호출할 때 인수 전달 됩니다
- await로 표시된 코드에서 발생할 수 있는 일시 중단 지점은 현재 코드조각이 비동기 함수 또는 메서드가 반환되기를 기다리는 동안 실행을 일시 중지할 수 있음을 나타냅니다. 스레드 양보라고 불린다.
- Task.yield() 메서드를 호출하여 일시 중단 지점을 명시적으로 삽입할 수 있습니다.
func generatsSlideshow(forGallery gallery: String) async { let photos = await listPhtos(isGallery: gallety) for photo in photos { // 비디오 랜더링을 상정하는 부분 await Task.yield() // 일시 중단 지점을 추가하여 다른 작업이 계속 진행될 수 있도록 함 } }
- Task.yield()는 비동기 함수 내에서 다른 코드가 실행될 수 있도록 현재 실행 중인 작업을 일시적으로 중단시키는 Swift의 동시성 기능입니다.
- 비디오 렌더링과 같이 시간이 오래 걸리는 작업을 처리할 때 Task.yield()를 사용하면 다른 작업의 중단 없이 효율적인 실행이 가능합니다.
- 이 메서드를 사용함으로써, Swift는 동시 실행되는 다른 작업과의 균형을 유지할 수 있게 됩니다.
비동기 시퀀스
- 다른 방법으로 비동기 시퀀스를 사용하여 한번에 컬렉션의 한 요소를 기다리는 것입니다.(시퀀스 반복하는 방법)
let handle = FileHandle.standardInput for try await line in handle.bytes.lines { print(line) }
- await를 사용하여 for문을 씁니다. 비동기 함수 또는 메서드를 호출할 떄와 마찬가지로 await를 작성하면 가능한 일시 중단 지점을 나타냅니다. await루프는 다음 요소를 사용할 수 있을 때까지 대기할 때 각 반복이 시작될때 실행을 일시 중단 합니다.
- await 키워드가 추가되어 각 반복문이 시작될때마다 다음 요소가 사용 가능할때까지 일시중단될수있음을 나타낸다.
- 다음 요소가 준비 될때까지 기다리면서, 그 사이에 다른 비동기 작업을 수행할 수 있습니다!!
- await 키워드가 추가되어 각 반복문이 시작될때마다 다음 요소가 사용 가능할때까지 일시중단될수있음을 나타낸다.
비동기 함수를 병렬로 호출하기
- await를 사용하여 비동기 함수를 호출하면 한 번에 하나의 코드만 실행됩니다. 비동기 코드가 실행되는 동안 호출자는 해당 코드가 완료될때까지 기다렸다가 다음 코드 줄을 실행합니다.
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)
- 이 접근 방식에는 중요한 단점이 있습니다 -> 다운로드가 비동기식이고 진행되는 동안 다른작업이 수행되도록 하지만 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)
- 이렇게 하게 되면 세번의 호출이 모두 이전 호출이 완료될때까지 기다리지 않고 시작 됩니다. 시스템이 좋다면 동시에 실행도 가능
- 호출의 결과가 필요하므고 세장의 사진이 모두 다운로드를 마칠때까지 실행을 일시 중지하기 위해 await를 씁니다.
- 하나의 함수를 반복해서 쓰게 된다면 그것이 await라면 선언하게 될때 async를 써서 병렬로 호출하게 한다.
- 여기서 강조된 주요 개념은 async let을 사용하여 동시에 여러 비동기 작업을 시작하고, 이들이 완료될 때까지 기다리기 위해 await를 사용하는 것입니다. 이 방식은 각 작업이 독립적으로 진행되면서도 최종적으로 모든 작업이 완료된 후에 결과를 처리할 수 있게 해줍니다.
작업 및 그룹작업
- Task는 프로그램의 일부를 비동기적으로 실행할 수 있는 작업 단위 입니다.
- 모든 비동기 코드는 일부 작업의 일부로 실행됩니다. Task자체는 한번에 1가지만 하지만, 여러 Task를 생성할때
Swift
는 Task가 동시에 실행되도록 스케줄링할 수 있습니다.Task
-> 비동적으로 실행되는 작업 단위 입니다. 여러 Task를 생성할때Swift
는 이들을 자동으로 동시에 실행하도록 스케줄링합니다.TaskGroup
-> 이것을 사용하면 관련된 작업들을 그룹화하여 관리할 수 있습니다.
- 작업은 계층 구조로 정렬됩니다.
- 지정된 작업에는 동일한 부모, 자식 작업으로 나뉩니다.
- 상위 작업에서는 하위 작업이 완료될떄 까지 기다리는 것을 잊지 말아야 합니다.
- 자식 작업에 더 높은 우선 순위를 설정하면 부모 작업의 우선 순위가 자동으로 에스컬레이션됩니다.
- 부모 작업이 취소되면 각 자식 작업도 자동으로 취소 됩니다.
- 작업 로컬 값은 자식 작업에 효율적이고 자동으로 전파 됩니다.
여러장의 사진을 처리하고, 사진을 다운로드하는 코드 await withTaskGroup(of: Data.self) { group in let photoNames = await listPhotos(inGallery: "Summer Vacation") for name in photoNames { group.addTask { return await downloadPhoto(named: name) } } for await photo in group { show(photo) } }
- 지금 코드는
withTaskGroup
을 사용하여 비동기적으로 여러 사진을 다운로드하는 과정입니다.listPhotos(inGallery:)
를 통해서 사진 이름 목록을 얻은후, for문을 사용해 각 사진 이름에 대해group.addTask
를 호출하여 사진을 다운로드하는 작업을 그룹에 추가합니다. 이후 또다른for await
를 사용하여 각 사진이 다운로드될때마다show(photo)
를 호출하여 사진을 표시 합니다.- 여러 비동기 작업을 동시에 처리하고, 각 작업이 완료되는 대로 결과를 처리할 수 있도록 해줍니다.
클로저 결과값을 추가한 코드
let photos = await withTaskGroup(of: Data.self) { group in
let photoNames = await listPhotos(inGallery: "Summer Vacation")
for name in photoNames {
group.addTask {
return await downloadPhoto(named: name)
}
}
var results: [Data] = []
for await photo in group {
results.append(photo)
}
return results
}
- 이전 코드는
show
함수를 사용하여 표시하였지만, 결과는 저장하지않았습니다 - 이번 고크는 다운로드된 사진을
results
라는 배열에 저장하고, 모든 작업후에 배열을 반환하는 코드입니다. - 이렇게하면 다운로드된 사진들을 추후에 사용하기 위해 저장하거나 다른 처리를 할 수 있게 됩니다. 좀 더 유연성있는 코드가 됩니다?
작업 취소
- 취소가 가능하다! Task 실행중 적절한 시점에 취소되었는지 확인하고 취소에 적절히 대응이 가능합니다.
- Task가 수행하는 작업에 따라, 취소에 대응하는 방법은 이렇게 나뉩니다.
CancellationError
같은 에러를 던지거나nil
또는 빈 컬렉션을 반환하거나- 부분적으로 완료된 작업을 반환하는 것입니다.
- Task 취소 여부를 확인하고 취소된 경우 실행을 중단하는 방법은 두가지가 있습니다.
Task.checkCancellation()
메서드를 호출하거나Task.isCancelled
속성을 읽는 것입니다.checkCancellation()
을 호출하면 Task가 취소된 경우 에러를 던지며, 이는 Task의 모든 작업을 중단 시킬수 있습니다.- 보다 유연하게 처리를 위해서는
isCancelled
속성을 사용할 수 있습니다.
let photos = await withTaskGroup(of: Optional<Data>.self) { group in
let photoNames = await listPhotos(inGallery: "Summer Vacation")
for name in photoNames {
group.addTaskUnlessCancelled {
guard isCancelled == false else { return nil }
return await downloadPhoto(named: name)
}
}
var results: [Data] = []
for await photo in group {
if let photo { results.append(photo) }
}
return results
}
- 위코드는 비동기적으로 여러 사진을 다운로드하는 예제 코드입니다.
Optional<Data>
타입을 사용하여, 사진 다운로드 Task가 취소 되었을 경우 nil을 반환할 수 있도록 합니다.- listPhotos(inGallery:)를 통해 갤러리의 사진 이름 목록을 얻습니다.
- 각 사진 이름에 대해,
group.addTaskUnlessCancelled
를 사용하여 태스크를 추가합니다. 여기서isCancelled
를 확인하여 Task가 취소되었는지 검사하고, 취소된 경우 nil을 반환합니다. - for
await
루프를 사용하여 그룹의 각 Task가 완료될 때마다 결과를results
배열에 추가합니다.nil
이 아닌 경우에만 배열에 추가합니다. - 모든 Task가 완료되면 완성된 사진 배열
results
를 반환합니다. - 비동기 Task의 취소 가능성을 고려하여 보다 안정적인 동시성 처리를 구현합니다.
- 즉시 취소 알림이 필요한 작업의 경우
Task.withTaskCancellationHandler(operation:onCancel:)
메서드를 사용합니다. let task = await Task.withTaskCancellationHandler { // ... } onCancel: { print("Canceled!") } // ... some time later... task.cancel() // Prints "Canceled!"
'iOS' 카테고리의 다른 글
비동기 테스트 (1) | 2024.07.08 |
---|---|
컴파일 최적화 방법 (0) | 2024.05.05 |
스파게티코드 해결방안 고민하기 (3) | 2024.04.15 |
OOP (객체 지향 프로그래밍) (1) | 2024.01.08 |
POP(프로토콜 지향 프로그래밍) (0) | 2024.01.08 |