티스토리 뷰

Defining and Calling Asynchronous Functions

비동기 함수 또는 비동기 메서드는 실행 도중 일시 중단될 수 있는 특수한 종류의 함수 또는 메서드입니다.

이는 완료될 때까지 실행되거나 오류를 발생시키거나 절대 return하지 않는 일반적인 동기화 함수 및 메서드와는 대조적입니다.

비동기 함수 또는 메서드는 여전히 이 세 가지 중 하나를 수행하지만, 무언가를 기다리고 있을 때 중간에 일시 중지할 수도 있습니다.

비동기 함수 또는 메서드의 바디 안에서 실행이 일시 중단될 수 있는 각 위치를 표시합니다.

 

함수 또는 메서드가 비동기임을 나타내려면 throws를 사용하여 던지기 함수를 표시하는 방법과 유사하게 매개 변수 뒤에 async 키워드를 씁니다.

함수 또는 메서드가 값을 반환하는 경우 반환 화살표(->) 앞에 async를 씁니다.

예를 들어, 갤러리에서 사진 이름을 가져오는 방법은 다음과 같습니다.

func listPhotos(inGallery name: String) async -> [String] {
    let result = // ... some asynchronous networking code ...
    return result
}

비동기식 및 던지기 함수나 메서드의 경우 throws 전에 async를 씁니다.

 

비동기 메서드를 호출하면 해당 메서드가 반환될 때까지 실행이 일시 중단됩니다.

호출 앞에 await라고 적으면 중단 가능 지점을 표시합니다.

이는 던지기 함수를 호출할 때 쓰는 try와 같습니다.

오류가 있을 경우 프로그램 흐름에 변경 가능성을 표시합니다.

비동기식 메소드 내에서는 다른 비동기식 메소드(서스펜션은 절대 암시적이거나 선제적이지 않음)를 호출할 때만 실행 흐름이 일시 중단됩니다.

즉, 가능한 모든 중단 포인트가 await로 표시됩니다.

 

예를 들어, 아래 코드는 갤러리에 있는 모든 사진의 이름을 가져온 다음 첫 번째 사진을 표시합니다.

let photoNames = await listPhotos(inGallery: "Summer Vacation")
let sortedNames = photoNames.sorted()
let name = sortedNames[0]
let photo = await downloadPhoto(named: name)
show(photo)

listPhotos(inGallery:)와 downloadPhoto(named:) 함수는 모두 네트워크 요청을 수행해야 하므로 완료하는 데 비교적 오랜 시간이 걸릴 수 있습니다.

리턴 화살표 앞에 async를 작성하여 둘 다 비동기 상태로 만들면 이 코드가 사진이 준비될 때까지 기다리는 동안 앱의 나머지 코드가 계속 실행됩니다.

 

위 예제의 동시 특성을 이해하기 위해 가능한 실행 순서는 다음과 같습니다.

  1. 코드는 첫 번째 줄부터 실행되기 시작하여 첫 번째 await까지 실행됩니다. listPhotos (inGallery:) 함수를 호출하고 해당 함수가 반환될 때까지 기다리는 동안 실행을 일시 중단합니다.
  2. 이 코드의 실행이 일시 중단되는 동안 동일한 프로그램의 일부 다른 동시 코드가 실행됩니다. 예를 들어, 장기간 실행되는 백그라운드 태스크가 새 사진 갤러리 목록을 계속 업데이트할 수 있습니다. 또한 이 코드는 await로 표시된 다음 일시 중단 지점까지 실행되거나 완료될 때까지 실행됩니다.
  3. listPhotos(inGallery:)가 반환된 후 이 코드는 해당 지점부터 실행을 계속합니다. photoNames로 반환된 값을 할당합니다.
  4. sortedNames과 name을 정의하는 줄은 일반적인 동기 코드입니다. 이 라인에는 await로 표시된 것이 없기 때문에 정지 지점이 없습니다.
  5. 다음 await는 downloadPhoto(named:) 함수에 대한 호출을 표시합니다. 이 코드는 해당 함수가 반환될 때까지 실행을 다시 일시 중지하여 다른 동시 코드를 실행할 수 있는 기회를 제공합니다.
  6. downloadPhoto(named:)이 반환되면 반환 값이 사진에 할당된 다음 show(_:)를 호출할 때 인수로 전달됩니다.

await로 표시된 코드의 일시 중단 가능한 지점은 비동기 함수 또는 메서드가 반환될 때까지 기다리는 동안 현재 코드 조각이 실행을 일시 중지할 수 있음을 나타냅니다.

이를 *yielding the thread(*스레드 넘겨주기)이라고도 하는데, 백그라운드에서는 Swift가 현재 스레드에서 코드 실행을 일시 중단하고 대신 해당 스레드에서 다른 코드를 실행하기 때문입니다.

await 코드는 실행을 일시 중단할 수 있어야 하므로 프로그램의 특정 위치에서만 비동기 함수 또는 메서드를 호출할 수 있습니다.

  • 비동기 함수, 메서드 또는 프로퍼티에 있는 코드
  • @main으로 표시된 구조체, 클래스, 열거형의 static main() 메서드의 코드
  • Unstructed Concurrency에서 보게 될 unstructured child task에 있는 코드

 

일시 중단 가능한 지점 사이의 코드는 다른 동시 코드의 방해 없이 순차적으로 실행됩니다.

예를 들어, 아래 코드는 한 갤러리에서 다른 갤러리로 사진을 이동합니다.

let firstPhoto = await listPhotos(inGallery: "Summer Vacation")[0]
add(firstPhoto toGallery: "Road Trip")
// At this point, firstPhoto is temporarily in both galleries.
remove(firstPhoto fromGallery: "Summer Vacation")

add(: to Gallery:) 호출과 remove(: from Gallery:) 호출 사이에 다른 코드가 실행될 수 없습니다.

이 기간 동안 첫 번째 사진이 두 갤러리에 모두 나타나 앱의 불변량 중 하나를 일시적으로 깨뜨립니다.

이 코드 덩어리가 나중에 await를 절대 가지지 않아야 한다는 것을 명확하게 하기 위해 해당 코드를 동기화 함수로 리팩터링할 수 있습니다.

func move(_ photoName: String, from source: String, to destination: String) {
    add(photoName, to: destination)
    remove(photoName, from: source)
}
// ...
let firstPhoto = await listPhotos(inGallery: "Summer Vacation")[0]
move(firstPhoto, from: "Summer Vacation", to: "Road Trip")

위의 예에서는 move(_:from:to:)함수가 동기화되어 있으므로 중단점을 절대 포함할 수 없음을 보장합니다.

앞으로 이 함수에 동시 코드를 추가하여 일시 중단 가능성이 있는 지점을 도입하려고 하면 버그를 도입하는 대신 컴파일 타임 오류가 발생합니다.

NOTE

Task.sleep(until:tolerance:clock:) 메서드는 동시성이 어떻게 작동하는지 배우기 위해 간단한 코드를 작성할 때 유용합니다. 이 메서드는 아무 작업도 수행하지 않지만 지정된 시간(나노초) 동안 기다렸다가 다시 시작합니다.

sleep(until:tolerance:clock:) 을 사용하여 네트워크 작업 대기를 시뮬레이션하는 listPhotos(inGallery:) 입니다.

func listPhotos(inGallery name: String) async throws -> [String] {
    try await Task.sleep(until: .now + .seconds(2), clock: .continuous)
    return ["IMG001", "IMG99", "IMG0404"]
}

[iOS] Concurrency(3) - 비동기 시퀀스, 비동기 함수 병렬 호출

댓글
공지사항