[번역] Task (애플 공식 문서)

삭제된 Velog·2025년 4월 12일
0

Swift Cocurrency

목록 보기
1/7
post-thumbnail

본 글은 Task (애플 공식 문서)를 한국어로 번역하여 옮긴 글입니다.

비동기 작업의 한 단위

iOS 13.0+ | iPadOS 13.0+ | Mac Catalyst 13.0+ | macOS 10.15+ | tvOS 13.0+ | visionOS 1.0+ | watchOS 1.0+

@frozen
struct Task<Success, Failure> where Success : Sendable, Failure : Error

Overview

Task의 인스턴스를 생성할 때, 작업(task)이 수행할 일(work)이 담긴 클로저를 전달해야 합니다. 작업(task)은 생성되자마자 곧바로 실행되며, 명시적으로 시작하거나 스케줄링을 할 필요가 없습니다. 작업을 생성하면 인스턴스를 활용해 해당 작업과 상호작용할 수 있습니다. 예를 들어, 작업이 완료되기까지 기다리거나, 취소를 할 수 있습니다. 작업이 끝나기까지 기다리거나 취소를 하지 않고도 작업에 대한 참조(reference)를 유지하지 않는 건 프로그래밍 에러가 아닙니다. 작업은 개발자가 해당 작업에 대한 참조를 유지하는지 여부와 관계없이 실행됩니다. 그러나, 만약 작업에 대한 참조를 유지하지 않는다면, 작업의 결과를 기다리거나 취소를 할 수 있는 능력을 잃어버리게 됩니다.

현재 작업이 Detached Task이든 하위 작업(child task)이든 관계없이, 해당 작업에 대한 연산을 지원하기 위해 Taskyield()와 같은 클래스 메서드를 제공합니다. 해당 메서드들은 비동기(asynchronous)적이기 때문에, 항상 기존 작업의 일부로서 호출됩니다.

작업의 일부로서 실행되는 코드만 해당 작업과 상호작용할 수 있습니다. 현재 작업과 상호작용하려면 Task 정적 메서드 중 하나를 호출하세요.

작업의 실행은 작업이 실행되는 시점들의 연속(series of periods)으로 볼 수 있습니다. 이러한 각 시점은 일시 중지 지점(suspension point)이나 작업이 완료되는 시점에서 종료됩니다. 작업의 이러한 실행 시점은 PartialAsyncTask의 인스턴스로 표시될 수 있습니다. 직접 사용자화 실행자(executor)를 구현하지 않는 한, 국부적인 작업과 직접 상호작용할 필요가 없습니다.

Task가 포함된 언어 수준의 동시성 모델에 대한 자세한 내용은 The Swift Programming LanguageConcurrency를 참조하세요.

Task Cancellation

작업에는 취소를 처리하기 위한 공유 메커니즘이 포함되어 있지만, 취소를 어떻게 처리할지는 공통된 방식으로 구현되어 있지 않습니다. 작업에서 수행하는 일의 성격에 따라서, 작업을 중단하는 적절한 방법이 달라질 수 있기 때문입니다. 더욱이, 작업이 중단되더라도 취소 여부를 확인하는 것은 작업의 일부로 실행되는 코드의 책임입니다. 여러 단계로 구성된 오래 걸리는 작업은 중간중간 취소 여부를 확인해야 하며, 각 지점마다 취소를 다르게 처리해야 할 수도 있습니다. 작업을 중단하기 위해 단순히 예외만 던지면 되는 경우, Task.checkCancellation() 메서드를 호출하여 작업이 취소되었는지 확인할 수 있습니다. 작업 취소에 대한 다른 대응 방식으로는 지금까지 완료된 작업을 반환하거나, 빈 결과 또는 nil을 반환하는 것이 있습니다.

취소는 단지 불리언(Boolean) 상태일 뿐이며, 취소의 사유와 같이 추가적인 정보는 포함할 수는 없습니다. 이는 작업이 여러 가지 사유로 취소될 수 있으며, 취소 과정 중에 추가적인 사유가 발생할 수 있다는 점을 보여줍니다.

Task closure lifetime

작업은 실행할 코드를 담고 있는 클로저를 전달하며 초기화됩니다.

클로저 내부의 코드가 실행을 완료하면, 작업은 실패 또는 결과 값을 반환하며 완료됩니다. 이때 클로저는 즉시 해제됩니다.

작업이 보유한 모든 참조는 작업이 완료된 후 해제되기 때문에 작업 객체를 유지한다고 해서 클로저가 무기한으로 참조되는 게 아닙니다. 따라서, 작업은 값에 대한 약한 참조(weak reference) 캡처를 할 필요가 거의 없습니다.

예를 들어, 아래 코드 조각에서 액터(actor)를 약한 참조로 캡처할 필요는 없습니다. 이는 작업이 완료될 때 액터에 대한 참조를 해제하면서, 작업과 액터 간의 순환 참조(reference cycle)를 끊기 때문입니다.

 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
    }
 }

그리고 아래와 같이 호출할 수 있습니다.

await Worker().start()

액터는 start() 메서드에서 사용되는 self에 의해 참조된다는 점을 주목하세요. 그리고, start 메서드는 구조화되지 않은 동시성(unstrucutured concurrency)인 Task가 완료되기를 기다리지 않고 즉시 반환합니다. 이후 작업이 완료되어 해당 작업의 클로저가 해제되면, 액터를 향한 강한 참조 역시 해제되어 예상한 대로 액터가 소멸(deinitialize)될 수 있게 됩니다.

따라서 위 호출은 아래와 같은 일관된 출력을 보여줍니다.

start task work
completed task work
deinit actor
profile
rlarjsdn3.github.io

0개의 댓글