[Swift] Task

2024. 10. 25. 00:03·Swift

아침에 9시부터 일어나서 바로 학습정리 중이다..레전드 갓생.. 하지만 블로그 글이 올라가는 시간은 12시.....껄껄 ~

오늘은 Task에 대해서 알아볼 예정이다.ᐟ.ᐟ 왜냐 ~ 그냥 ~

Apple 공식문서에 따르면 Task는 비동기 작업의 단위를 의미한다.

모든 비동기 코드는 task의 한 부분으로서 실행된다.
Task는 한 번에 하나만 실행될 수 있는데, Task를 여러 개 만듦으로써 Swift가 여러 개의 Task를 동시에 실행시킬 수 있도록 스케줄링할 수 있다.

init(priority: TaskPriority?, operation: sending () async -> Success), init(priority: TaskPriority?, operation: sending () async throws -> Success) 와 같은 방식으로 인스턴스를 생성할 수 있으며, operation 매개변수에 들어가는 클로저가 우리가 실행할 작업이라고 생각하면 된다.

이 외에 간단한 Task의 특징들은 다음과 같다.

  • Task는 생성과 동시에 따로 명시적으로 시작하거나 스케줄링을 하지 않아도 바로 실행시킬 수 있다.
  • Task가 생성되고 난 후에는 인스턴스를 사용해 작업이 완료될 때까지 기다리거나 취소시키는 등의 상호작용이 가능하다.
  • Task의 작업이 끝나거나 취소되기 전에 Task의 참조를 없애는 것이 가능하다. 하지만 참조를 없애면 작업의 결과를 받거나 취소시키는 것은 불가능해진다.

Task Group

TaskGroup을 사용하여 child task 등의 상속관계를 만들 수 있고, 이를 활용하여 각각의 Task 우선순위나 cancellation등을 더 효과적으로 제어할 수 있다.

Task는 계층 구조로 배열될 수 있는데, 지정된 TaskGroup 내부의 각 task들은 동일한 상위 작업(parent task)이 있고, 각각의 task들은 하위 작업(child task)을 가질 수 있다. 이러한 접근 방식을 구조화된 동시성(structured concurrency)라고 부른다.
이와 같은 명시적인 parent-child 관계의 task들은 아래와 같은 이점들을 갖는다.

  • 상위 작업에서는 하위 작업이 완료될 때까지 기다려야한다.
  • 하위 작업이 높은 작업의 우선순위를 가지고 있다면, 상위 작업의 우선순위는 자동적으로 더 높아진다.
  • 상위 작업이 취소되면 자동적으로 하위 작업들도 취소된다.
  • Task-local value들은 효율적이고 자동으로 하위 작업에 전파된다.

마지막 이점인 Task-local value들이 효율적으로 하위 작업에 전파된다는 뜻은.. 정확하게 이해하지 못했다.. 🫠

TaskGroup은 withTaskGroup(of:)메서드를 활용해서 만들 수 있다.

아래 코드는 withTaskGroup(of:)메서드를 활용하여 task group을 만드는 예제 코드이다.

func listPhotos(inGallery name: String) async -> [String] {
    return ["IMG001", "IMG99", "IMG0404"]
}

func downloadPhoto(named: String) async -> Data { return Data() }

func show(_ photo: Data) { }

await withTaskGroup(of: Data.self) { group in
    let photoNames = await listPhotos(inGallery: "Summer Vacation")
    for name in photoNames {
        group.addTask {
            return await downloadPhoto(named: name)
        }
    }

    for await photo in group {
        show(photo)
    }
}

withTaskGroup(of:)메서드 내부에 addTask메서드를 활용하여 group 내부에 Task를 추가하는 것을 확인할 수 있다. 이렇게 추가된 task들은 두 번째 for문 내부에서 show함수를 실행시키게 되는데, 이 때 각각의 Task들이 group에 append되는 순서는 뒤죽박죽이기때문에 photoNames의 순서와 show가 되는 이미지의 순서는 다를 수 있다.

Task Cancellation

Swift Concurrency는 협력적 취소 모델(cooperative cancellation model)을 사용한다.
각각의 작업은 적절한 시점에 취소 여부를 확인하고 응답한다. 각각의 작업에 따라 취소에 대해 응답한다는 것은 일반적으로 다음 중 하나를 의미하게 된다.

  • CancellationError 와 같은 에러를 던진다.
  • nil 혹은 빈 collection을 반환한다.
  • 부분적으로 완료된 작업을 반환한다.

이미지 다운로드와 같은 작업은 그 이미지의 크기가 너무 크거나 네트워크가 불안정하면 시간이 오래걸릴 수 있다. 이러한 경우 task가 완료될 때까지 기다리지 않고 유저가 작업을 중단할 수 있게 해야하는데, 이러한 작업을 하기 위해선 task의 취소를 확인해야한다. task의 취소를 확인할 수 있는 방법엔 다음 2가지가 있다.

  • Task.checkCancellation() 타입 메서드 호출
  • Task.isCancelled 타입 프로퍼티 읽기

아래 예시는 이미지의 string 배열을 받아오는 async 함수 listPhotos(inGallery:)를 통해 이미지 이름을 가져오고 TaskGroup에 각각의 이미지 이름에 맞는 이미지를 다운받는 task를 append하는 flow이다.

let photos = await withTaskGroup(of: Optional<Data>.self) { group in
    let photoNames = await listPhotos(inGallery: "Summer Vacation")
    for name in photoNames {
        let added = group.addTaskUnlessCancelled {
            guard !Task.isCancelled else { return nil }
            return await downloadPhoto(named: name)
        }
        guard added else { break }
    }

    var results: [Data] = []
    for await photo in group {
        if let photo { results.append(photo) }
    }
    return results
}

맨 처음 for문에서 .addTaskUnlessCancelled 메서드를 활용하여 해당 TaskGroup이 취소되었는지 확인한다. 이후 isCancelled 타입 프로퍼티를 활용하여 task가 취소되었는지 확인하고, 취소되지 않은 task의 경우 group에 append한다. 만약 TaskGroup이 취소되었거나 child task 중 하나가 취소되면 그 즉시 첫 번째 for문이 종료되고 group에 append 되어있는 task에 있는 이미지들만 results 배열에 append되어 반환되게된다.

여기서 잘 이해가 안가는 부분은 첫 번째 for문 내부의 guard문인데.. 어떻게 child task가 취소되었는지 확인하는건지… 이해가 잘 가지 않는 부분이다..

해당 이미지의 첫 번째 목차를 직독직해해보면 isCancelled 타입 프로퍼티를 사용해서 task의 취소를 확인하는 것 같은데.. 도대체 어떻게 확인하는것인지 이해가 잘 가지 않는다;;;

Task closure lifetime

위에서도 잠깐 언급되었던 것 같이 Task는 후행클로저를 통해 실행될 작업을 생성할 수 있다.

이렇게 생성된 클로저 내부의 코드는 작업이 완료되고 나면 메모리에서 해제된다. Task 객체가 남아있다고 해도, 해당 Task가 completion되고 나면 내부의 참조는 해제됨으로 따로 약한 참조(weak self)를 해 줄 필요는 없다.

아래 예시코드를 보자.

struct Work: Sendable {}

actor Worker {
    var work: Task<Void, Never>?
    var result: Work?

    deinit {
        // even though the task is still retained,
        // once it completes it no longer causes a reference cycle with the actor
        print("deinit actor")
    }

    func start() {
        work = Task {
            print("start task work")
            try? await Task.sleep(for: .seconds(3))
            self.result = Work() // we captured self
            print("completed task work")
            // but as the task completes, this reference is released
        }
        // we keep a strong reference to the task
    }
}

/// start task work
/// completed task work
/// deinit actor

Task의 클로저 내부에서 self 캡처하는 것을 확인할 수 있다. 하지만 해당 클로저의 작업이 끝나면 참조는 해제되고 Task와 actor(Worker) 사이의 순환참조가 없어지기 때문에 따로 약한 참조를 해 줄 필요가 없는 것이다.

위에서도 언급했듯이 웬만한 경우엔 Task 클로저 내부에서 약한 참조를 할 필요가 없지만 아ㅏㅏㅏ주 간혹가다 Task 내부에서도 약한참조를 써야할 경우도 있긴 한 것 같다.. 예를 들면 async 함수가 매우매우매우 delay 되어서 끝나지 않는 경우..? 정확하진 않지만..

굉장히.. 러프하게 concurrency의 Task에 대해 알아보았다.
Actor의 isolation을 먼저 했어야했는데... 모종의 이유로 일단 그건 미루기 ^_^.... 다음에 정리할세요 ~

참고자료

 

Task | Apple Developer Documentation

A unit of asynchronous work.

developer.apple.com

 

Documentation

 

docs.swift.org

 

Visualize and optimize Swift concurrency - WWDC22 - Videos - Apple Developer

Learn how you can optimize your app with the Swift Concurrency template in Instruments. We'll discuss common performance issues and show...

developer.apple.com

 

저작자표시 (새창열림)

'Swift' 카테고리의 다른 글

[Swift] Actor(1)  (4) 2024.10.21
[Swift] Property wrapper  (0) 2024.10.06
[Swift] Swift 빌드 과정  (2) 2024.09.08
[Swift] Value Type과 Reference Type  (4) 2024.09.01
'Swift' 카테고리의 다른 글
  • [Swift] Actor(1)
  • [Swift] Property wrapper
  • [Swift] Swift 빌드 과정
  • [Swift] Value Type과 Reference Type
00me
00me
얼렁뚱땅 방장이 운영하는 기술 블로그
  • 00me
    영미의 iOS 다이어리
    00me
  • 전체
    오늘
    어제
    • 프로그래밍 (29)
      • 📖 (0)
      • CS (4)
      • Python (5)
      • Swift (5)
      • iOS (3)
      • 코테 (3)
        • 자료구조 (0)
        • 알고리즘 (3)
      • 회고 (9)
  • 링크

    • 🍧 GitHub
  • 인기 글

  • hELLO· Designed By정상우.v4.10.3
00me
[Swift] Task
상단으로

티스토리툴바