해당 글은 스위프트 컨커런시 4편과 이어집니다. 물론 1편 부터 보시고 오시길 바랍니다.
시작하겠습니다.
저번시간에 이어서 이제는 구조화가 된 Task 는 무엇을 보장하는지 알아보도록 하겠습니다.
import Foundation
func fetchData(url: URL) async throws -> Data {
let (data, _) = try await URLSession.shared.data(from: url)
return data
}
func performSequentialTasks() async {
let url1 = URL(string: "https://KingJaeHyung.com/1")!
let url2 = URL(string: "https://KingJaeHyung.com/2")!
do {
let data1 = try await fetchData(url: url1)
print("Data 1: \(data1)")
try await Task.sleep(for .second(2))
let data2 = try await fetchData(url: url2)
print("Data 2: \(data2)")
} catch {
print("Error: \(error)")
}
}
Task {
let task = Task {
do {
try await performSequentialTasks()
} catch {
print("Task was cancelled due to error: \(error)")
}
}
// 1초 후에 작업을 취소할게요
Task {
try await Task.sleep(nanoseconds: 1_000_000_000)
task.cancel()
}
}
저번 시간 코드를 가져와 보았습니다.
하지만 무언가 다른 부분이 있죠 위에 코드를 자세히 다시 봐보겠습니다.
상위의 테스크를 1초후에 취소 하도록 짜보았습니다.
async await는 하나의 Task 가 취소되면 해당 Task의 모든 하위 Task 자동으로 취소됩니다.
좀더 명확하게 생각해 보죠.
이렇게 작성해 보면 어떨까요?
import Foundation
func fetchData(url: URL) async throws -> Data {
let (data, _) = try await URLSession.shared.data(from: url)
return data
}
func performSequentialTasks() async throws {
let url1 = URL(string: "https://KingJaeHyung.com/1")!
let url2 = URL(string: "https://KingJaeHyung.com/2")!
let data1 = try await fetchData(url: url1)
print("Data 1: \(data1)")
try await Task.sleep(for: .seconds(2))
let data2 = try await fetchData(url: url2)
print("Data 2: \(data2)")
}
Task {
do {
try await performSequentialTasks()
} catch {
print("Error: \(error)")
}
}
performSequentialTasks
함수는 fetchData 함수를 호출하고, await 키워드를 사용하여 비동기 작업이 완료될 때까지 기다립니다.import Foundation
func fetchData(url: URL) async throws -> Data {
let (data, _) = try await URLSession.shared.data(from: url)
return data
}
// 무한 루프로 해보죠
func performInfiniteTasks() async throws {
let url = URL(string: "https://KingJaeHyung.com/1")!
while true {
// 취소 여부 확인
if Task.isCancelled {
print("Task was cancelled")
break
}
// 데이터 가져오기
let data = try await fetchData(url: url)
print("Data: \(data)")
// 작업 사이에 1초 대기
try await Task.sleep(for: .seconds(1))
}
}
// 메인 실행
let task = Task {
do {
try await performInfiniteTasks()
} catch {
if Task.isCancelled {
print("Task was cancelled")
} else {
print("Error: \(error)")
}
}
}
// 5초 후에 작업 취소
Task {
try await Task.sleep(for: .seconds(5))
task.cancel()
}
이번엔 Group Task 에 대해서 알아보도록 하겠습니다.
여러개의 비동기 작업을 Group화 하여 관리할수 있는 기능
즉, 여러 비동기 작업을 병렬로 실행하고, 모든 작업이 완료될때까지 기다릴수도 있지만,
따로 결과를 처리할수도 있습니다.
func fetchData(url: URL) async throws -> Data {
let (data, _) = try await URLSession.shared.data(from: url)
return data
}
// 배열로된 URL을 병렬로 처리할 함수
func fetchMultipleData(urls: [URL]) async throws -> [Data] {
return try await withThrowingTaskGroup(of: Data.self) { group -> [Data] in
var results = [Data]()
// 각 URL 작업을 그룹에 추가
for url in urls {
group.addTask {
return try await fetchData(url: url)
}
}
// 각 비동기 작업의 결과를 수집
for try await result in group {
results.append(result)
}
return results
}
}
Task {
let urls = [
URL(string: "https://KingJaeHyung.com/1")!,
URL(string: "https://KingJaeHyung.com/2")!,
URL(string: "https://KingJaeHyung.com/3")!,
URL(string: "https://KingJaeHyung.com/4")!,
]
do {
let results = try await fetchMultipleData(urls: urls)
for (index, data) in results.enumerated() {
print("결과 \(index + 1): \(data)")
}
} catch {
print(error)
}
withThrowingTaskGroup
를 통해 Task Group을 생성할수 있습니다.
그룹에 추가된 Task는Group 블록 범위 만큼만
수명을 가질수 있습니다.
오류를 발생시킬수 있는 Task(하위) 를 위해 범위가 지정된Group Object를 제공
합니다.
현재 위 코드들은 특히 Group Task를 다루다가 @Sendalble에
대해서 다루고 나서 좀더 깊이 다루어야 겠다 라는 생각이 들어
이번편은 가볍게 여기까지 하도록 하겠습니다. 다음 편에선
@Sendable 과 Data Race Safety는 뭘까
를 다루어 보도록 하겠습니다.
감사합니다.