티스토리 뷰

반응형

[1] TaskGroup과 ThrowingTaskGroup

 

TaskGroup 

: A group that contains dynamically created child tasks.  (자세한 설명은 이 글 5번 참고)

 

taskGroup을 만드려면 

withTaskGroup(of:returning:body:) 를 호출.

 

 

ThrowingTaskGroup

: A group that contains throwing, dynamically created child tasks.

 

throwing task group을 만드려면

withThrowingTaskGroup(of:returning:body:) 를 호출

 

 

[2] 사용예제 

 

2.1 간단한 병렬 실행을 위해 async-let syntax 를 쓰다가 확장이 필요할 때

 

여러장의 이미지를 병렬로 fetch 해와야하는 상황이라고 해봅시다. 

저는 성공한 케이스들만 UIImage 배열로 리턴해줄 것 입니다. 

 

이미지 fetch 메소드는 아래와 같고 (여기서 코드참고했어요) 

private func fetchImage(urlString: String) async throws -> UIImage {
    guard let url = URL(string: urlString) else {
        throw URLError(.badURL)
    }

    do {
        let (data, _) = try await URLSession.shared.data(from: url)
        if let image = UIImage(data: data) {
            return image
        } else {
            throw URLError(.badURL)
        }
    } catch {
        throw error
    }
}

 

예를들어 2개의 연산 정도 되는 간단한 병렬 실행이면 async let 쓰겠지만..

func fetchImages() async -> [UIImage] {
    async let fetchImage1 = fetchImage(urlString: "https://picsum.photos/300")
    async let fetchImage2 = fetchImage(urlString: "https://picsum.photos/300")

    let (image1, image2) = await (try? fetchImage1, try? fetchImage2)
    return [image1, image2].compactMap { $0 }
}

 

이미지 여러개를 가져오도록 확장되었을 때는 async-let syntax 를 쓰기 힘들어집니다;;; 

async let fetchImage1 = fetchImage(urlString: "https://picsum.photos/300")
async let fetchImage2 = fetchImage(urlString: "https://picsum.photos/300")
async let fetchImage3 = fetchImage(urlString: "https://picsum.photos/300")
async let fetchImage4 = fetchImage(urlString: "https://picsum.photos/300")
async let fetchImage5 = fetchImage(urlString: "https://picsum.photos/300")

let (image1, image2, image3, ...) = await (try? fetchImage1, try? fetchImage2, ...)

 

이 때 TaskGroup을 씁니다. 

async-let syntax 도 내부적으로 child task 를 생성하는 데,  이와 동일하게 직접 child task를 생성하는 셈입니다. 

 

에러를 던지지 않을 것이니까 withTaskGroup을 사용해줍니다. 

메소드 시그니쳐가 좀 복잡한데 두개만 잘 체크해주면 됩니다. 

- of 는 ChildTask 의 결과 타입 
- body는 group -> GroupResult 

func withTaskGroup<ChildTaskResult, GroupResult>(
    of childTaskResultType: ChildTaskResult.Type,
    returning returnType: GroupResult.Type = GroupResult.self,
    body: (inout TaskGroup<ChildTaskResult>) async -> GroupResult
) async -> GroupResult where ChildTaskResult : Sendable

 

먼저 withTaskGroup 메소드로 TaskGroup을 만들고 

addTask(priority:operation:) 로 group 에 childTask 를 추가합니다. 

func fetchImages() async -> [UIImage] {
    let urlStrings = [
        "https://picsum.photos/300",
        "https://picsum.photos/300",
        "https://picsum.photos/300",
        "https://picsum.photos/300",
        "https://picsum.photos/300"
    ]

    let images = await withTaskGroup(of: UIImage?.self) { group in
        for urlString in urlStrings {
            group.addTask {
                try? await self.fetchImage(urlString: urlString)
            }
        }
    }

    return []
}

 

이제 GroupResult (이미지 배열) 를 리턴해주는 코드를 추가합니다. 

func fetchImages() async -> [UIImage] {
    let urlStrings = [
        "https://picsum.photos/300",
        "https://picsum.photos/300",
        "https://picsum.photos/300",
        "https://picsum.photos/300",
        "https://picsum.photos/300"
    ]

    let images = await withTaskGroup(of: UIImage?.self) { group in
        for urlString in urlStrings {
            group.addTask {
                try? await self.fetchImage(urlString: urlString)
            }
        }

        var images: [UIImage] = []
        for await image in group {
            if let image {
                images.append(image)
            }
        }
        return images
    }

    return images
}

 

참고로 TaskGroup은 AsyncSequence 를 conform 하고 있기 때문에

reduce 를 사용해서 간결하게 코드를 작성할 수도 있습니다. 

func fetchImages() async -> [UIImage] {
    let urlStrings = [
        "https://picsum.photos/300",
        "https://picsum.photos/300",
        "https://picsum.photos/300",
        "https://picsum.photos/300",
        "https://picsum.photos/300"
    ]

    let images = await withTaskGroup(of: UIImage?.self) { group in
        for urlString in urlStrings {
            group.addTask {
                try? await self.fetchImage(urlString: urlString)
            }
        }

        return await group
            .reduce(into: [UIImage?](), { $0.append($1) })
            .compactMap { $0 }
    }

    return images
}

 

최종코드

func fetchImages() async -> [UIImage] {
    let urlStrings = [
        "https://picsum.photos/300",
        "https://picsum.photos/300",
        "https://picsum.photos/300",
        "https://picsum.photos/300",
        "https://picsum.photos/300"
    ]

    return await withTaskGroup(of: UIImage?.self) { group in
        for urlString in urlStrings {
            group.addTask {
                try? await self.fetchImage(urlString: urlString)
            }
        }

        return await group
            .reduce(into: [UIImage?](), { $0.append($1) })
            .compactMap { $0 }
    }
}

 

 

 

To Be Continued...

addTask 때  TaskPriority 를 세팅하는 예제를 찾아보기! 

 

 

반응형

'🍏 > Swift' 카테고리의 다른 글

[Swift] actor  (0) 2023.04.10
[Swift] Sendable  (0) 2023.04.08
[Swift] Understanding Swift Performance 를 보면서 알게 된 것  (0) 2023.03.04
[Swift Collections] deque  (1) 2022.10.08
[Swift] some, any 키워드  (2) 2022.07.16
댓글