Skip to content

Structured Concurrency (구조적 동시성) #7

@camosss

Description

@camosss

반복문 내부의 async 함수

func fetchImages(urls: [String]) async -> [UIImage] {
    var images: [UIImage] = []
    for url in urls {
        let image = await fetchImage(url) // 각 반복마다 기다림
        images.append(image)
    }
    return images
}
  • for + await 조합은 순차 실행 -> 한 이미지 다운로드가 끝나야 다음 반복으로 진행

    • 비동기지만, 동시에 실행되는 게 아니라 순서대로 실행됨
  • Concurrency의 await은 특정 스레드에서만 실행되는 게 아님

    • await에서 Task가 멈췄다가 재개될 때는 다른 스레드에서 이어질 수 있음

    • 특정 스레드를 점유하지 않고 필요 시 CPU를 양보

Thread 1  ───────────────────────────────────────────────

Task
Thread 2   task1 실행 ---- await ──┐
                                   │ (중단)
Thread 3        └────── task2 실행 ---- await ──┐
                                                │ (중단)
Thread 4                 └────── task3 실행 ---- await ── 끝

구조적 동시성

  • 부모 Task와 자식 Task 간 명확한 계층 관계 형성

  • 자식 Task는 동시로 실행되고, 모두 완료되어야 부모가 종료

부모 Task
 ├─ 자식 Task 1 ─ 완료
 ├─ 자식 Task 2 ─ 완료
 └─ 자식 Task 3 ─ 완료
       ↓
   모든 자식 종료 → 부모 Task 종료
  • Concurrency의 구조적 동시성

    • async let (암시적 하위 작업 생성)
    • TaskGroup (동적/확장성 있는 하위 작업 관리)
  • 메타데이터 상속

    • 자식 작업은 부모 작업의 우선순위, 실행 액터, Task-Local 변수를 상속
  • 취소 전파 (협력적 취소)

    • 부모가 취소되면 자식도 취소 전파
    • 단, Task는 즉시 종료되지 않고 “취소 상태”만 전달됨

async let

  • async let으로 여러 비동기 작업을 동시에 실행 시작

  • 필요 시점에 await으로 결과 수집

  • async let으로 만든 값은 해당 스코프 안에서 await로 소비 필요

Task {
    // 동시에 실행 시작
    async let image1 = fetchImage(num: 1)
    async let image2 = fetchImage(num: 2)
    async let image3 = fetchImage(num: 3)
    async let image4 = fetchImage(num: 4)

    // 결과를 기다릴 때 중단
    let result1 = try await image1
    let result2 = try await image2
    let result3 = try await image3
    let result4 = try await image4

    imageArray.append(contentsOf: [result1, result2, result3, result4])
}
  • 아래와 같이 작성해도 무방
// 튜플로 묶기
let (r1, r2, r3, r4) = try await (image1, image2, image3, image4)

// 옵셔널 처리 포함
let (r1, r2, r3, r4) = await (try? image1, try image2, try image3, try image4)

// inline await
let results = (try await image1,
               try await image2,
               try await image3,
               try await image4)

TaskGroup

  • 동적/대규모 동시 실행에 적합

  • 모든 작업이 완료될 때까지 자동으로 기다려줌

  • 반복문 안에서 Task를 추가하고, 완료되는 순서대로 결과를 받아올 수 있음

    • -> 입력 순서와 수집 순서는 다를 수 있음
func fetchImages(urls: [String]) async -> [UIImage] {
    await withTaskGroup(of: UIImage?.self) { group in
        for url in urls {
            // 반복문 안에서 원하는 만큼 Task를 추가
            group.addTask { await fetchImage(url) }
        }

        var results: [UIImage] = []

        // 작업이 끝나는 순서대로 결과를 수집
        for await image in group {
            if let image { results.append(image) }
        }

        // 그룹 전체 결과 반환
        return results
    }
}

DispatchGroup vs Structured Concurrency

DispatchGroup (GCD)

  • enter/leave 수동 관리 → 누락/중복 시 버그 위험

  • 취소/에러 전파 없음 → 직접 구현 필요

  • 결과 흐름이 콜백 기반이라 코드가 분산됨

Structured Concurrency (async let, TaskGroup)

  • 부모-자식 관계로 자동 관리

  • 모든 자식 Task가 끝나야 부모 Task 종료

  • 취소·에러 자동 전파, 코드 흐름이 직관적


Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions