Tasks and Task Groups
- Task
- 비동기 작업 단위
- Task는 한 번에 한 가지만 수행하지만 여러 Task를 작업할 경우, Task Group을 통해 동시에 실행되도록 해줄 수 있음
- async-let
- 계층 구조의 작업이 가지는 이점
- 상위 작업에서 하위 작업이 완료될 때까지 대기
- 하위 작업에서 작업의 우선순위를 높인다면 상위 작업의 우선순위도 함께 상향됨
- 상위 작업이 취소되면 하위 작업이 자동으로 취소됨
- 작업 내 데이터들이 하위 작업에서도 효율적으로 쓰일 수 있음
- 작업 결과를 반환하지 않는 TaskGroup
// 작업의 그룹 생성
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)
}
}
// 이미지 다운로드가 끝나면 show
for await photo in group {
show(photo)
}
}
// 모든 하위 작업이 끝날 때까지 대기 후 반환
let photos = 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)
}
}
var results: [Data] = []
for await photo in group {
results.append(photo)
}
return results
}
Task Cancellation
- 작업의 취소 상태를 확인해 적절한 대응 가능
- CancellationError 같은 에러 발생
- nil 또는 빈값 반환
- 부분 완료 작업 반환
- 작업 취소 방식
- Task.checkCancelation()
- Task.isCancelled
- 작업을 안전히 정지하거나 네트워크 통신을 중지, 임시 데이터를 지울 때
- 해당 작업 외부에서 작업이 취소되었는지 확인하는 타입 프로퍼티
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 {
// 작업이 취소됐다면 nil 반환
guard !Task.isCancelled else { return nil }
// 취소되지않았다면 이미지 다운로드
return await downloadPhoto(named: name)
}
guard added else { break }
}
// 그룹은 nil값을 스킾하고 반환
var results: [Data] = []
for await photo in group {
if let photo { results.append(photo) }
}
return results
}
- 만약 취소된 상황을 즉시 알고싶다면 Task.withTaskCancellationHandler 활용
- 취소 여부를 확인후 작업을 조기 종료시킴
- 해당 핸들러가 실행되면 작업과 해당 핸들러 간 상태를 공유하지 않아야 함
let task = await Task.withTaskCancellationHandler {
// ...
} onCancel: {
print("Canceled!")
}
// ... some time later...
task.cancel() // Prints "Canceled!"
Unstructured Concurrency
- 상위 작업이 없는 상태에서 비동기 작업이 일어나는 경우
- 작업 생성 시, Task.init(priority:operation:)
- 분리된 작업 생성 시, Task.detached(priority:operation:) 사용
👀 etc
@Sendable
- Data Race 없이 Concurrency 작업 간 공유할 수 있음을 의미하는 데이터 타입
- 채택 가능 타입
- Value types
- Reference types with no mutable storage
- Reference types that internally manage access to their state
- Functions and closures (by marking them with @Sendable)
- 컴파일러 적용없이 Sendable 채택 시, @unchecked Sendable 활용
- 같은 파일 내에 있어야한다는 규칙을 비활성화 시킴
- Lock 또는 Queue로 해당 상태에 대한 모든 접근을 보호해 확인되지않은 sendable의 책임을 짐
⇒ 문제가 생기더라도 개발자가 책임을 진다
- wwdc 내 왈 : 클로저 내에서 mutable 변수를 캡쳐하는 것이 제한됨
- 작업이 시작된 후 수정될 수 있기때문
- = 공유자원은 여러 스레드에서 접근해도 안전해야함
- ex) Actor, Class 등