[!note] 사람 뇌가 참 무섭다...분명 이해하고 넘어갔다고 생각했는데 기억이 잘 나지 않는다. 그럼... 다시 보는 수밖에 없을 듯.
1. async & await
- async: 이 함수가 비동기 함수라는 표시. 결과가 바로 안 나올 수도 있다는 뜻
- await: 비동기 함수의 결과를 기다린다는 키워드. await이 없으면 결과가 오기 전에 넘어간다.
func fetchData() async -> String { return "서버 데이터" } func run() async { let result = await fetchData() print(result) // "서버 데이터" } 2. async let
- 여러 개의 비동기 작업을 동시에 시작하고, 나중에 필요할 때 결과를 await으로 받아온다.
- 병렬 실행용!
func run() async { async let user = fetchUser() async let posts = fetchPosts() // 둘 다 끝날 때까지 기다렸다가 값을 얻음 let (u, p) = await (user, posts) print(u, p) } - 차이점: let 앞에 async 붙이면 "동시에 시작만 해놓고, 나중에 필요할 때 기다릴 수 있음"
3. Task
- 새로운 비동기 작업을 시작하는 방법
- Task { ... }는 현재 위치랑 별개로 새로운 "작업"을 만들어 실행한다.
- 백그라운드에서 실행되는 느낌
Task { let result = await fetchData() print(result) } - Task는 트리 구조가 있어서, 부모 작업이 있으면 우선순위나 취소 상태를 이어받을 수도 있다.
4. 예제(전부 사용해보기)
func fetchUser() async -> String { "User" } func fetchPosts() async -> [String] { ["Post1", "Post2"] } func run() async { // 동시에 실행 async let user = fetchUser() async let posts = fetchPosts() // 다른 독립적인 Task 실행 Task { print("백그라운드에서 실행 중") } // 결과 기다리기 let (u, p) = await (user, posts) print(u, p) } 구조화되지 않은 동시성
구조화되지 않은 동시성이라고 하는 개념인 Task 객체를 사용하여 개별 작업을 직접 만들 수 있다. 이미 보았듯 구조화되지 않은 작업의 일반적인 용도는 동기 함수 내에서 비동기 함수를 호출하는 것이다.
구조화되지 않은 작업은 실행 중 언제든지 외부에서 취소할 수 있기 때문에 더 많은 유연성을 제공한다. 이는 백그라운드 다운로드 작업을 중지하기 위해 버튼을 누르는 것과 같이 백그라운드 활동을 취소하는 방법을 사용자에게 제공해야 하는 경우에 특히 유용하다. 이러한 유연성에는 작업을 만들고 관리하기 위해 더 많은 작업을 수행해야 하는 측면에서 약간의 부가적인 노력이 필요하다.
구조화되지 않은 작업은 Task 생성자를 호출하고 수행될 코드가 포함된 클로저를 제공하여 생성되고 시작된다.
Task { await doSomething() } - 또한 이러한 작업은 액터 콘텍스트(곧 배운다), 우선순위, 그리고 작업 로컬 변수 등의 호출되는 부모의 구성을 상속한다. 작업을 만들 때, 작업에 새로운 우선순위를 지정할 수도 있다.
Task(priority: .high) { await doSomething() } - 이것은 다른 작업과 관련하여 작업을 어떻게 스케줄링할지에 대한 힌트를 시스템에 제공한다. 가장 높은 것부터 가장 낮은 것까지 사용 가능한 우선순위는 다음과 같다.
.high / .userInitiated .low / .utility .medium .background - 작업이 직접 생성되면 Task 인스턴스에 대한 참조를 반환한다. 이것은 작업을 취소하거나 작업 범위 외부에서 작업이 이미 취소되었는지 여부를 확인하는 데 사용할 수 있다.
let task = Task(priority: .high) { await doSomething() } . . if (!task.isCancelled) { task.cancel() } 이해해보기
1. 구조화된 동시성 VS 구조화되지 않은 동시성
구조화된 동시성(async/await, async let)
- 부모-자식 구조로 자동으로 관리되는 작업
- 부모가 끝나면 자식도 끝나야 함
- 따로 신경 안 써도 안전하게 정리됨
구조화되지 않은 동시성(Task {...})
- 내가 직접 만든 독립적인 작업
- 부모랑 상관 없음. 직접 취소, 관리해줘야 함.
작업 그룹
지금까지는 하나 또는 두 개의 작업(부모 및 자식)을 만들었다. 각각의 경우, 우리는 코드를 작성하기 전에 얼마나 많은 작업이 필요한지 배웠다. 하지만 동적인 조건에 따라 여러 작업을 동시에 생성하고 실행해야 하는 상황이 종종 발생한다.
예를 들어 배열의 각 항목이나 for 루프 안에서 별도의 작업을 시작해야 하는 경우가 있을 수 있다. 스위프트는 작업 그룹을 제공하여 이러한 상황을 해결한다.
작업 그룹을 사용하면 가변적인 수의 작업을 생성하고 withThrowingTaskGroup() 또는 withTaskGroup() 함수(그룹의 비동기 함수에서 오류가 발생하는지 여부에 따라 다름)를 사용하여 구현할 수 있게 한다. 그런 다음, 작업을 생성하기 위한 루프 구조가 해당 클로서 내에서 정의되고 addTask() 함수를 호출하여 각각의 새로운 작업을 추가한다.
[!note] 머리에 내용이 잘 들어오지 않는다. 데이터 경쟁 피하기, for-await 구문 - 이것도 결국 작업 그룹 관련 이야기인데 생각 정리가 잘 안 된다. 비동기 속성 부분도 그렇다.
시간 많이 들여서 노력했는데 이해가 잘 안돼서 속상하긴 하다. 하지만 포기하지 말자. 다음에 보면 이해가 되어있을 지도 모른다.