일전에 번역해서 그대로 올렸더니, 문제가 되는 것 같아서 비공개로 전환했던 글이다.
새로 정리하여 재 작성했다.
처음에는 원문을 그대로 살리는 데 집중했다면, 이번에는 내용을 이해해보려고 노력했다.
Let's dive in.
said Kavon
Structured concurrency in Swift
- Swift 5.5 의 새로운 동기화 기법. (structured programming 에서 영감을 얻어서 만듬.)
구조적 프로그래밍이란?
- 코드의 control-flow를 순차적(top-down)으로 읽을 수 있도록 구조화한 프로그래밍 방식.
- if-else 가 대표적인 예제
- 코드의 블럭화(Swift에서는 static scoping이라고 부름)로 구조적 프로그래밍이 가능.
- 블럭 내 (if절 내 or else 절 내) 별도의 life time을 가지기 때문에 보다 쉽게 코드를 이해 할 수 있다.
static scope으로 코드를 작성하는 구조적 프로그래밍을 이용하면 제어 흐름(control-flow)과 변수의 라이프타임(variable lifetime)을 보다 쉽게 이해할 수 있다.
- Kavon
비동기 코드와 구조적 프로그래밍
- 현재는 구조적 프로그래밍이 자연스러운 방식이지만 비동기 코드(asynchronous code)와 동시성 코드(concurrent code)에는 적용되지 않는 방식이다.
- 비동기로 실행한 결과를 처리하는 동작을 (top-down)으로 처리할 수 었었기 때문이다.
- completion handler 로 나중에 실행되도록 처리해야 한다.
Task in Swift
- Task는 Swift의 새로운 기능으로 async function끼리 밀접하게 연결되어 일을 처리하도록 한다.
- Task는 새로운 Execution Context 에서 async code를 실행하고, 각 Task 는 서로 다른 Execution Context를 방해하지 않고 실행된다.
- 자동으로 스케쥴링되고 안전하고 효과적으로 병렬처리 된다.
- Swift 언어는 concurrency bug를 컴파일 단에서 찾아내서 문제가 발생하지 않도록 예방한다.
- 개발자가 직접 명시적으로 Task를 생성해야 한다.(자동으로 생성하지 않는다.)
because structured concurrency is about balance between flexibiliby and simplicity.
구조적 동시성은 유연함과 간결함 사이의 균형이기 때문이다.
said Kavon
Async-let tasks
Seauential bindings
- 가장 간결한 Task.
- let 바인딩 전 비동기에 사용할 값 초기화 ➡️ 비동기 코드를 실행 ➡️ 결과를 비동기 후처리 코드에서 수행
- 위 이미지와 같은 코드는 단 한 번만 수행되고, 에러 처리도 불가능하다.
Concurrent binding
- 위 코드에 재사용성을 추가하고, 에러 핸들링도 추가해야 실제로 사용이 가능하다.
- 데이터를 실제로 사용하기 전까지 계속 다운 받아야 한다.
- async 코드를 추가해서 아래 그림과 같이 async-let이라고 불리는 concurrent binding을 해야 한다.
How to work async-let
- child task 생성(concurrent binding을 생성한 task의 subtask)
- Assign placeholder(to Parent task, 흰색) 와 Evaluate Initializer(to Child Task, 초록색) 화살표가 동시에 발생
- parent task는 child task 가 다운로드 받는 사이에 다음 코드(following statements)를 수행
- 하지만 코드를 수행하기 위해서는 child task에서 결과 값이 필요해서 기다려야 함.(await)
- 경우에 따라 error 가 발생할 수도 있으므로 try가 필요.
순차 바인딩(Sequential binding)
- 이미지 다운로드 - 이미지 사이즈 다운로드 를 순차로 실행하는 경우
- try-await 를 let의 오른쪽에 사용해야 한다.
- 두 작업을 비동기로 생성하기 위해 두 개의 async 코드를 작성. (async let(data,_), async let(metadata,_))
- try-await 코드는 metadata 와 image data를 읽는 코드에 작성한다.
- 다운로드가 발생하는 task는 child task.
- concurrent binding이 발생할 때, 컴파일 단에서 해당 task를 관찰해서 체크하기 때문에 비동기 작업이 생성될 때 try await 할 필요가 없다.
- Child Task의 Task Tree 는 계층 구조.
- cancellation, priority, task-local variables 과 같은 요소에 영향을 준다.
- 📌 Concurrently bound variables를 사용할 때는 메소드 콜과 같은 다른 변경을 가하면 안된다.
- async 함수내에서 async 함수 호출이 발생하는 경우 : 같은 task 내에서 함수 호출이 수행된다.
- priority나 task value와 같은 origin task의 모든 속성을 상속 받아 task가 생성된다.
Structured task Guarantees
- structured task는 현재 task의 하위(child) task로 생성된다.
- child task 의 모든 작업이 종료되어야만, parent task가 종료 될 수 있다.
- 첫번째 await task에서 에러를 던지면 fetchOneThumbnail 함수는 즉각적으로 종료되면 좋지만, 사실 몇가지 과정을 거친다.
- Cancellation propagates (취소 전파)
- 첫번째 await task에서 에러를 던진다.
- 나머지(에러를 던진 이후의 task) task를 취소함으로써 unwaited task로 표시한다.
- task에게 결과가 필요하지 않다고 알려주고, 끝나길 기다린다.(stop 시키는 게 아니고, cancel 시키는 것.)
- task의 subtask들도 같은 방법으로 cancel 하고 subtask들이 끝나길 기다린다.
- sutask들이 cancel 상태로 종료된다.
- 남은 task들도 cancel 상태로 종료된다.
- finally fetchOneThumbnail task 가 종료된다.
- fetchOneThumbnail 함수는 error를 던지고 자신이 직간접적으로 만든 모든 structured task가 종료되고 나면 끝난다.
- 취소 전파(Cancellation propagates)는 Structured concurrency의 기본이다.
- ARC가 자동으로 메모리 Liftime을 관리하는 방법과 유사하게 TASK를 관리한다.
Cancellation is cooperative
- 만일 task가 중요한 작업 중이거나 네트워크 연결을 유지중이라면, 단순히 작업을 멈추는 건 좋지 않다.
- 코드가 언제든지 취소될 수 있도록 디자인 되어야 한다.
Group Tasks
- Task Group. concurrency 양이 동적일 때 사용하기 위해 만든 structured concurrency.
- async- let 보다 유연함.
- 정해진 갯수의 비동기 task를 운용할 때는 async-let이 더 유리.
- 예제.
- for 루프 안에서 2개의 child task를 생성하고 있다.( 썸넬 이미지 받는 task, id 받는 task)
- 2개의 child task가 모두 끝나야 다음번 루프가 실행 될 수 있다.
- for 루프의 ids가 몇개인지 알 수 없고, 너무 많은 경우도 있을 것이다.
- 이 때, Task Group 을 사용할 수 있다.
How to Use Group Tasks
- withThrowingTaskGroup 이용해서, 해당 영역 내 group object로 child task 를 만든다.
- task는 그룹에 추가 됨. (group.async)
- group내에서 생성된 task는 그룹의 블럭 영역 내에서만 라이프 사이클으르 가진다.
- child task는 그룹 내에서 순서와 상관없이 즉시 실행된다.
- 모든 task의 completion은 group object를 벗어나서 존재 하고, task가 종료 될 때까지 기다린다.
- group task 도 structured concurrendcy 이므로 task tree 규칙을 따른다.
About Data race issues
- 아래 이미지에서 thumbnails dictionary는 공유 자원
- 각 child task 가 서로 thumbnail을 넣으려고 하기 때문에 data race 이슈가 발생한다. -> crash or data corruption
- Swift에서는 이를 static checking 해주고 있다!(WOW)
- task는 @Sendable이라는 새로운 closure를 이용해야 한다.
@Sendable Closure
- mutable 변수가 캡쳐되지 않도록 방지하는 역할.
- mutable 변수는 task 실행 중에도 변경 될 수 있는 값이기 때문이다.
- for more detailed : WWDC2021 Sesstion - "Protect mutable state with Swift actors"
- 위와 같은 경우에는 String Id와 UIImage thumbnail을 튜플로 반환 받기를 추천
- for-await는 각 child task의 결과를 차례로 던질 수 있다. for-await 루프는 순차적이라서 parent task에서도 하나씩 처리 할 수 있다.
- for more detail : WWDC21 Session - "Meet AsyncSequence"
Task Group vs Async-Let
- task tree 규칙이 조금 다르다.
- task group의 결과 중에 컴파일 error를 발생 시킨 child task를 만난 경우
- 해당 error는 group의 블럭 밖으로 던져지고, group 내 모든 task 가 취소되어 대기하는 상태가 된다. (like asyn-let)
- task group이 정상 종료되어 블럭을 빠져나간다.
- task의 상태는 취소가 아닌 대기(await)상태가 된다. (async-let의 취소전파와 다른 방식)
- 필요에 따라 cancelAll 메소드를 호출해줘야 한다.
Keep in mind that no matter how you cancel a task, cancellation automatically propagates down the tree.
명심해야 할 것은 task를 취소하는 방식이 무엇이든 취소 전파는 task tree로 전파 되어 내려간다는 것이다.
said Kavon
Unstructured Tasks
- Task 생성 시 origin context의 속성을 상속 받으면서도 독립된 영역에서 실행되는 Task.
- Task의 계층 구조에 딱 맞지 않는 경우
- Parent task가 필요없거나 non-async에서 async를 만들어야 하는 경우.
- 보다 유연성이 필요한 경우들
- Task의 라이프타임을 단일 영역(single scope)이나 함수 내로 한정 할 수 없을 때
- 예를 들어 객체가 호출되어 active 상태가 될때 시작되고, 다른 메소드가 호출되며 객체가 deactive 상태가 되면 실행을 취고 하고 싶은 경우
give you a lot more flexibility at the expense of needing a lot more manual management
보다 많은 유연성을 제공하고, 보다 많은 코드 관리비용이 든다.
said Joe
UIKit/Appkit 과 Unstructed task
- AppKit과 UIKit으로 delegate object를 구현
- UI는 main thread 에서 실행되어야 한다.( Swift는 UI class를 main actor 에 포함 시켜서 이를 보장)
ConllectionView에서..
- collectionView item에 썸넬이 화면에 노출될 때만 fetchThumbnail 함수를 호출하려고 한다.
- delegate method는 async가 아님. 따라서 await를 쓸 수 없다.
- async를 위한 task 실행
- task는 delegate action에 응답해서 실행된다.
- 이후, 화면을 그리기 위해서는 main actor에서 UI priority로 실행되어야 한다.
- 이 경우, 이 task의 실행을 한 scope으로 한정 시킬 수가 없다.
- 그래서 Unstructed task.
How to work the Unstructed task
- Runtime 에 일어나는 3가지 일.
- task 생성 시, 이를 생성한 영역의 actor에 생성된 task가 스케쥴된다. (여기서는 main actor)
- control은 원래 task로 돌아간다.
- thumbnail task가 실행될 때 main thread를 blocking 하지 않고 main thread에서 실행된다.
- Unstructured code와 Structured code의 중간 지점정도 되는 구성이라고 볼 수 있다.
Make the Unstructed task code
- actor상속 받기
- priority 및 다른 속성들을 원래의 context 로 부터 상속받아 구현.
- task의 lifetime은 특정 scope으로 한정되지 않는다.(ex. like task group's block)
- task를 생성하는 곳이 async task가 아니어도 된다.
- 취소 전파, 에러 핸들링을 직접 관린해야 한다.
- task의 결과도 await상태로 동작하도록 직접 구현해야 한다.
Cancel the Unstructed task code
- thumbnail 이 스크롤 되어 화면에서 사라질 때, thumbnail task가 취소되도록 작업할 예정.
- Unstructed task는 자동 취소 되지 않는다. (lifecycle scope이 없기 때문)
- task-row index 를 value-key 로 활용하여 dictionary로 만들어야 원하는 task를 찾아서 취소 할 수 있다.
- task가 삭제되고 나면 dictionary에서 key-value 쌍이 제거되어야 한다.
- async task 안팎에서 row index-task dictionary를 사용해야 한다. data racing 없이!
- delegate 는 main actor 에서 동작. thumbnail task는 main task의 속성을 상속 받아 만들어진 task.
- 따라서 두 task가 병렬로 실행되지 않는다.
- data racing(데이터 경합) 걱정없이 안전하게 main actor 클래스에 저장된 프로퍼티를 thumbnail task 에서도 사용할 수 있다.
- delegate 는 main actor 에서 동작. thumbnail task는 main task의 속성을 상속 받아 만들어진 task.
- delegate가 이미 화면에서 사라진 row 에 작업을 지시하면, cancel 메소드를 호출해서 해당 row의 task를 취소 할 수 있다.
Detached Tasks
- origin context의 어떤 속성도 상속 받지 않는 Task.
- 유연성을 극대화 한 unstructured task의 한 종류.
특징
- task를 호출한 영역(scope)의 생명주기를 전혀 따르지 않는다.
- origin context의 어떤 속성도 가려오지 않는다.
- actor, priority 등 모든 속성이 새로 구성된다.
- optional parameter를 전달 받아서 어떤 task를 실행할지 결정한다.
캐싱 구성
- 캐싱 작업은 main actor에서 이루어질 필요가 없다.
- 썸넬 fetching 작업이 도중에 취소되더라고, 취소 되기 전까지 가져온 썸넬은 캐시에 저장되어있다.
- detached task로 구현 해보자.
Detached task로 캐싱 구성하기
- 캐싱은 main UI 작업을 방해하지 않아야 한다. -> priority : .background
- 썸넬 작업과 함께 multiple background task 가 생기면?
- structured concurrency를 detached task에 활용하는 게 좋다. ( 별도로 background task를 생성해도 되지만... 추천하지 않음)
- task group으로 만들어서 각 background job을 task group의 child task처럼 구성할 수 있다.
- task group으로 구성함으로써 얻는 장점이 크다.
- top-level의 detached task 하나만 취소하면 다른 task들(child task)도 한번에 취소가 전파된다.
- child task들은 모두 background detached task의 속성을 상속 받아서, 매번 같은 구성을 반복해서 생성하지 않아도 되고, 속성을 누락하는 등의 실수도 방지 할 수 있다.
Wrap up
swift 에서 제공하는 task의 종류는 아래 4가지이다. 그리고 그 특징이 아래 표에 잘 정리 되어있다.
아래 나머지 세션을 통해 더 많은 내용을 확인하길 바란다.
“Meet async/await in Swift” 에서는 async 함수의 보다 자세한 내용을 확인하고 구조적 코딩 기반(structured basis) concurrent code 작성법을 볼 수 있다. actor가 데이터 고립(data isolation)을 제공해서 데이터 경합(data racing)으로부터 couccruent system을 안전하게 해준다.
*StudyInMySpareTime WWDC21 Meet async/await
“Protect mutable state with Swift actors” 에서는 task group 내 for wait 루프에 대해 배울 수 있다. AsyncSequence 의 예제들로 구성되어있다. 해당 예제들은 데이터의 비동기 흐름을 처리하는 표준 인터페이스를 제공한다.
*StudyInMySpareTime Protect mutable state with Swift actors #1<Actor, Actor Reentrancy>
“Meet AsyncSequence” 에서는 가용가능한 API를 더욱 깊게 살펴본다. core OS와 결합하여 오버헤드를 줄이고 확장성을 높인다.
“Swift concurrency: Behind the scenes” 에서는 기술적으로 상세하게 이야기한다.
All these features come together to make writing concurrent code in Swift easy and safe, letting you write code that gets the most out of your devices while still focusing on the interesting parts of your app, thinking less about the mechanics of managing concurrent tasks or the worries of potential bugs caused by multithreading.
이러한 모든 기능들은 Swift 내에서 concurrent code 쉽고 안전하게 작성하도록 돕고, 개발자가 코드를 작성할 때 앱의 중요한 부분에 집중할 수 있게 하고, concurrent task를 관리하기 위한 기술적인 고민과 멀티스레딩의 잠재적인 버그에 대한 걱정을 덜어준다.
said Joe
Watch WWDC21 Original Session Video
WWDC21 Session : Explore structured concurrency in Swift
이 블로그에서 WWDC21 Swift 비동기 프로그래밍 관련 글 보기.
StudyInMySpareTime WWDC21 Meet async/await
StudyInMySpareTime Protect mutable state with Swift actors #1<Actor, Actor Reentrancy>
StudyInMySpareTime WWDC21 Protect mutable state with Swift actors #2<Swift Isolation, Main Actor
'iOS' 카테고리의 다른 글
UITests Error가 발생할 때 No target application path specified via test configuration (0) | 2023.02.20 |
---|---|
Xcode14 Apple Clang Compiler gnu++20 (0) | 2023.01.03 |
Xcode Device build. 시스템 키체인을 사용하고자 합니다. (0) | 2022.03.16 |
Xcode에서 자꾸 인증을 하라고 한다. "Authentication failed because no credentials were provided" 대응. (0) | 2022.02.11 |
WWDC21 Protect mutable state with Swift actors #1<Actor, Actor Reentrancy> (0) | 2021.07.04 |