본문 바로가기

Swift/Concurrency

Discover Concurrency (2) - Asynchronous Task 지연시키기

다양한 비동기 작업에 딜레이를 추가하고 싶을 수 있다. 예를 들어, 다른 작업이 먼저 완료되기를 기다리거나 “debouncing” 작업을 추가하기 위한 것이다.

Swift에서는 특정 시간 지연을 가진 Task를 직접 실행하는 내장 방법은 제공하지 않고 있지만, 특정 나노 초만큼 Task를 대기하도록 지시함으로써 이러한 동작을 구현할 수 있다.

 

Task {
    // Delay the task by 1 second:
    try await Task.sleep(nanoseconds: 1_000_000_000)
    
    // Perform our operation
    ...
}

Task.sleep 은 다른 코드와 관련해 완전히 차단되지 않기 때문에 다른 sleep 메서드와는 다르다.

 

Task.sleep 에 try 키워드가 붙은 이유는 작업이 취소되었을 경우 에러를 throw 하기 때문이다.

 

만약 비동기 작업이 150밀리초 이상 소요된다면 뷰 컨트롤러에서 로딩 스피너를 표시하도록 하려고 한다면 다음과 같이 구현할 수 있다.

 

class VideoViewController: UIViewController {
    ...
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        let loadingSpinnerTask = Task {
            try await Task.sleep(nanoseconds: 150_000_000)
            showLoadingSpinner()
        }

        Task {
            await prepareVideo()
            loadingSpinnerTask.cancel()
            hideLoadingSpinner()
        }
    }
    
    ...
}

 

위 예제는 뷰 컨트롤러의 콘텐츠를 로드하기 위해 Task를 사용하는 방법에 대한 완벽한 예제는 아니다.

 

더 나은 방법은 새로운 작업을 시작하기 전에 이미 진행 중인 로딩 작업이 있는지 확인하는 것이다.

 

자세한 내용은 “What role do Tasks play within Swift’s concurrency system?” 을 참조
https://kswift.tistory.com/27

 

Discover Concurrency (1) - Task는 Swift Concurrency system내에서 어떤 역할을 할까?

개인적으로 WWDC 2022 - Eliminate data races using Swift Concurrency 에서 기본적인 Concurrency의 개념을 학습하고 나서 이 글을 읽으니 훨씬 이해하기 편했습니다. 아직 안 보셨다면 먼저 보시는 걸 추천드립니

kswift.tistory.com

 

만약 주어진 코드 베이스에서 많은 지연 작업을 사용할 계획이라면, 지연된 작업을 더 쉽게 생성할 수 있는 간단한 추상화를 정의하는 것이 좋을 수 있다.

 

예를 들어 나노초 대신 더 표준적인 TimeInterval값을 사용하여 초 단위의 지연을 정의할 수 있다.

extension Task where Failure == Error {
    static func delayed(
        byTimeInterval delayInterval: TimeInterval,
        priority: TaskPriority? = nil,
        operation: @escaping @Sendable () async throws -> Success
    ) -> Task {
        Task(priority: priority) {
            let delay = UInt64(delayInterval * 1_000_000_000)
            try await Task<Never, Never>.sleep(nanoseconds: delay)
            return try await operation()
        }
    }
}

try await Task<Never, Never>.sleep(nanoseconds: delay) 이부분에서 Task<Never, Never>인 이유는 Task 클래스의 sleep 메서드는 Never로 특수화된 Task<Never, Never> 형식에만 사용할 수 있기 때문이다.

 

이를 통해서 이제 Task.delayed를 호출하여 지연된 작업을 만들 수 있다. 이 방법의 유일한 단점은 이제 해당 작업 클로저 내에서 명시적으로 self를 캡처해야 한다는 것이다.

 

class VideoViewController: UIViewController {
    ...

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        let loadingSpinnerTask = Task.delayed(byTimeInterval: 0.15) {
    self.showLoadingSpinner()
}

        Task {
            await prepareVideo()
            loadingSpinnerTask.cancel()
            hideLoadingSpinner()
        }
    }
    
    ...
}

 

 

 

"semi-public" _implicitSelfCapture 속성은 내장된 Task 클로저 내에서 self 참조를 자동으로 캡처하기 위해 Swift 표준 라이브러리에서 사용된다.

Swift에서 클로저는 일반적으로 주변 컨텍스트에서 변수를 캡처하기 위해 명시적인 캡처 리스트가 필요하다. 그러나 Task 클로저의 경우 Swift 표준 라이브러리는 _implicitSelfCapture 속성을 사용하여 명시적인 캡처 리스트 없이 self 참조를 자동으로 캡처한다.

이 속성을 사용하면 Task 클로저의 본문 내에서 인스턴스 속성 및 메서드에 직접 액세스 할 수 있다.

 

따라서 Task.delayed 확장 메서드를 사용할 때 _implicitSelfCapture 속성을 사용함으로써 클로저 내에서 self를 수동으로 캡처하는 필요성을 피할 수 있다.

이렇게 함으로써 클로저는 캡처 리스트에 명시적으로 self를 캡처하지 않고도 self에 접근할 수 있게 된다.

extension Task where Failure == Error {
    static func delayed(
        byTimeInterval delayInterval: TimeInterval,
        priority: TaskPriority? = nil,
        @_implicitSelfCapture operation: @escaping @Sendable () async throws -> Success
    ) -> Task {
        ...
    }
}

 

하지만 위 속성은 밑줄로 시작되는 이름 때문에 Swift의 공식 API의 일부가 아니므로 production code에서 사용하는 것을 권장하지는 않는다. 사용할 경우 해당 속성을 사용하는 코드가 언제든지 중단될 수 있는 위험을 감수해야 한다.

 

 

출처

Delaying an asynchronous Swift Task | Swift by Sundell

 

Delaying an asynchronous Swift Task | Swift by Sundell

Most often, we want our various asynchronous tasks to start as soon as possible after they’ve been created, but sometimes we might want to add a slight delay to their execution — perhaps in order to give another task time to complete first, or to add s

www.swiftbysundell.com