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

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

Swift Cocurrency

목록 보기
7/7
post-thumbnail

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

태스크-로컬의 키 값을 정의하는 래퍼 타입(wrapper type)

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

final class TaskLocal<Value> where Value : Sendable

Overview

태스크-로컬 값은 작업(Task) 컨텍스트 내에서 바인딩하고 읽을 수 있는 값입니다. 태스크-로컬 값은 작업과 함께 암시적으로 전달(carried)되며, 이 작업이 생성하는 (TaskGroup이나 async-let에서 생성한 작업 등) 모든 하위 작업(child tasks)에서도 접근할 수 있습니다.

Task-local declarations

태스크-로컬은 반드시 정적(static) 프로퍼티나 전역 프로퍼티로 선언되어야 합니다.

enum Example {
	@TaskLocal
    static var traceID: TraceID?
}

// Global task local properties are supported since Swift 6.0:
@TaskLocal
var contextualNumber: Int = 12

Default values

태스크-로컬 값에 아무런 값을 바인딩하지 않고 읽으면 태스크-로컬의 기본값을 반환합니다. 태스크-로컬을 (TraceID?처럼) 옵셔널 타입으로 선언하는 경우, 기본값은 nil이 됩니다. 하지만 아래와 같이 태스크-로컬을 선언할 때 다른 기본값을 직접 지정할 수 있습니다.

enum Example {
	@TaskLocal
    static var traceID: TraceID = TraceID.default
}

기본값은 현재 작업이나 상위 작업 컨텍스트에 값이 바인딩되지 않았거나, 호출 스택(call stack) 상에 어떤 비동기 함수도 없는 동기 함수에서 태스크-로컬 값을 읽으려 할 때 반환됩니다.

Reading task-local values

태스크-로컬 값을 읽는 건 일반적인 정적 프로퍼티를 읽는 것처럼 간단합니다.

guard let traceID = Example.traceID else {
	print("no trace id")
    return
}
print(traceID)

태스크-로컬 값은 비동기 함수뿐만 아니라 동기 함수에서도 읽을 수 있습니다.

Binding task-local values

태스크-로컬 값은 직접 설정(set)할 수 없으며, 반드시 $traceID.withValue() { ... } 연산을 통해 바인딩해야 합니다. 이 값은 해당 범위가 유지되는 동안에만 바인딩되며, 이 범위 내에 생성된 모든 하위 작업에서도 사용할 수 있습니다.

Detached Task는 태스크-로컬 값을 상속하지 않습니다. 반면에, Task { ... }로 생성된 작업은 비록 구조화되지 않은 작업(unstructured task)이라 하더라도, 새로운 비동기 작업으로 값을 복사하는 방식으로 태스크-로컬 값을 상속합니다.

Using task local values outside of tasks

작업의 바깥에서 태스크-로컬 값을 바인딩하고 읽을 수 있습니다.

이는 작업 내에서 호출된다고 보장되지 않는 동기 함수에서 특히 유용합니다. 작업의 바깥에서 태스크-로컬 값을 바인딩하는 경우, 런타임은 작업과 동일한 저장 메커니즘을 사용하는 스레드-로컬(thread-local)을 설정합니다. 이는 아래 예제와 같이 특정 호출 컨텍스트를 염려할 필요 없이, 태스크-로컬 값을 안정적으로 바인딩하고 읽을 수 있음을 의미합니다.

func enter() {
	Example.$traceID.withValue("1234") {
    	read() // always "1234", regardless if enter() was called from inside a task or not:
    }
}

func read() -> String {
	if let value = Self.traceID {
    	"\(value)"
    } else {
    	"<no value>"
    }
}

// 1) Call `enter` from non-Task code
// 	  e.g synchronous main() or non-Task thread (e.g. a plain pthread)
enter()

// 2) Call `enter` from Task
Task {
	enter()
}

위에서 언급한 두 경우 모두에서, 태스크-로컬 값의 바인딩과 읽기는 예상대로 동작합니다.

Examples

enum Example {
	@TaskLocal
    static var traceID: TraceID?
}

func read() -> String {
	if let value = Self.traceID {
    	"\(value)"
    } else {
    	"<no value>"
    }
}

await Example.$traceID.withValue(1234) { // bind the value
	print("traceID: \(Example.traceID)") // traceID: 1234
    read() // traceID: 1234
    
    async let id = read() // async let child task, traceID: 1234
    
    await withTaskGroup(of: String.self) { group in
    	group.addTask { read() } // task group child task, traceID: 1234
        return await group.next()!
    }
}

Task { // unstructured tasks do inherit task locals by copying
	read() // traceID: 1234
}

Task.detached { // detached tasks do not inherit task-local values
read() // traceID: nil
}

⚪️ See Also
TaskLocal-macro

profile
rlarjsdn3.github.io

0개의 댓글