← 메인으로

0819-2 async-let 바인딩

이전의 예제 코드에서 await 키워드의 기본 동작은 실행을 재개하기 전에 호출된 함수가 반환될 때까지 기다리는 것이다. 그러나 더 일반적인 요구사항은 비동기 함수가 백그라운드에서 실행되는 동안 호출하는 함수 내에서 코드를 계속 실행하는 것이다.

이것은 async-let 바인딩을 사용해 해당 코드에서 나중까지 기다리는 것을 지연시켜서 할 수 있다. 이를 보여주기 위해 먼저 takesTooLong() 함수가 결과(여기서는 작업 완료 타임스탬프)를 반환하도록 수정하자.

func takesTooLong() async -> Date { // 이 함수는 실행 도중 멈출 수도 있다. 그러니까 호출하는 쪽에서 await으로 불러라. sleep(5) return Date() } 
func doSomething() async { // doSomething()도 비동기 함수다. print("Start \(Date())") // 첫 스탬프 찍고 async let result = takesTooLong() // takesTooLong() 실행 시작(비동기 예약) print("End \(Date())") } 
func doSomething() async { print("Start \(Date())") async let result = takesTooLong() print("After async-let \(Date())") // 비동기 함수와 동시에 실행할 추가 코드가 여기에 온다. print("result = \(await result)") print("End \(Date())") } 

[!note] 와... 전 단원에서 이해 좀 잘 된다고 생각했는데 또 무슨 소린지 모르겠다. async-let은 또 뭐야? 갑자기...?

  1. await의 기본 동작
    • await은 비동기 함수 안이나 Task 블록 안에서만 쓸 수 있다.
    • await을 만나면 그 지점에서 코드 실행이 멈추고, 결과가 돌아올 때까지 기다려야 한다. "이 블록 안에서는 무조건 기다려라"
  2. 문제점
    • 가끔은 "결과는 나중에 필요하니까 지금 당장 기다리고 싶지 않아"라는 상황이 있음.
    • 서버에서 이미지 3장을 받아와야 하는데, 한 장 끝날 때까지 멍하니 기다리는 게 아니라 3개를 동시에 요청하고 나중에 다 모였을 때만 기다리고 싶을 때
  3. async let의 역할
    • 비동기 작업을 예약만 해두고, 결과를 필요할 때까지 기다리는 걸 미룸
    • await: 지금 당장 기다림
    • async let: 기다림을 "나중"으로 미룸
[Start] → [Next] (뒤에서 동시에 5초 작업 진행 중) ↓ 나중에 결과 필요할 때 await result 

오류 핸들링

구조화된 동시성의 오류 핸들링은 전에 다룬(나는 대충 보고 넘어갔던...) throw/do/try/catch 메커니즘을 사용한다. 다음 예제는 기존의 비동기 takesTooLong() 함수를 수정하여 지연값을 매개변수로 받으며 만약에 지연이 특정 범위를 벗어나면 오류를 발생시킨다.

enum DurationError: Error { case tooLong case tooShort } func takesTooLong(delay: UInt32) async throws { if delay < 5 { throw DurationError.tooShort } else if delay > 20 { throw DurationError.tooLong } sleep(delay) print("Async task completed at \(Date())) } 

Task 이해하기

비동기적으로 실행되는 모든 작업은 스위프트의 Task 클래스의 인스턴스 내에서 실행된다. 앱은 여러 작업을 동시에 실행할 수 있으며 이러한 작업을 계층적으로 구성할 수 있다. 이번 장의 앱에서 버튼이 눌리면 비동기 버전의 doSomething() 함수가 Task 인스턴스 내에서 실행된다.

takesTooLong() 함수가 호출되면 시스템은 함수 코드가 실행될 하위 작업을 생성한다. 작업 계층 트리 측면에서 이 하위 작업은 상위 작업인 doSomething()의 자식이다. 하위 작업 내에서의 모든 비동기 함수 호출은 해당 작업의 자식이 되는 식이다.

이 작업 계층 구조는 구조화된 동시성이 구축되는 기반을 형성한다. 예를 들어 자식 작섭은 부모로부터 우선순위와 같은 속성을 상속하며 계층 구조는 모든 하위 작업이 완료될 때까지 부모 작업이 종료되지 않도록 한다. 뒤에서 보겠지만, 작업을 그룹화하여 여러 비동기 작업을 동적으로 시작할 수도 있다.

[!note] 설명을 더럽게 어렵게 한다. 쉽게 말하면 부모는 자식이 다 끝나야 끝날 수 있다는 것이고, 담는 함수가 부모라는 말을 하고 있는 것이다.

Task (버튼 눌러서 시작한 최상위) └── doSomething() Task └── takesTooLong() Task

이게 좋은 이유