이번에 미루고 미뤄왔던 코루틴을 공부해봤다.
이번에 공부했던 자료는 아래와 같다.
- 안드로이드 공식문서
- 코틀린홈에서 공식문서
- 안드로이드 코드랩 2개
코루틴 코드랩은 생각보다 별로였다.
하지만 코드랩에서 코루틴이 main-safe를 쉽게 만들 수 있다는 것을 일깨워준다.
예전에 봤던 컴포즈 basic코드랩에 있는 코루틴 코드랩이, 코루틴 입문하기에 가장 쉽고 적합한거 같다.
코루틴 처음하면, 아래 링크에서 코루틴 관련 내용을 학습하면 좋을 듯 하다.
https://developer.android.com/courses/pathways/android-basics-compose-unit-5-pathway-1
안드 공식홈은 간단하게 훑는 용이고(일단, 안드 공식홈에 있는 내용정도만 쓸 줄 알면 충분할 듯 하다),
코틀린 공식홈이 세세하게 설명이 되어있는데 이해가 안되는게 많았다.
전반적으로 코루틴을 이해하기에는 공식문서의 내용이 미흡한거 같다.
그래서 다른 자료를 찾아보는게 필요할텐데,
이 블로그에 코루틴에 대해서, 굉장히 자세히 정리되어 있는 것을 보았다.
나중에 궁금한게 생기면 여기서도 찾아봐야 할 듯하다.
https://myungpyo.medium.com/reading-coroutine-official-guide-thoroughly-part-0-20176d431e9d
여기서부터는 안드 공식홈과 코틀린 공식 홈에 있는 내용을 직접 번역해보면서 메모한 내용이다.
내용이 방대하고, 공식 문서에 있던 내용을 내가 생략한 것도 많아서 다른분들이 보기에는 적절하지 않고
내가 기억안나는 키워드를 검색하는 용으로 쓰려고 넣어뒀다.
안드 공식홈에
코루틴 소개 + 고급 코루틴 개념을 보고 받아쓰면서 정리한 글이다.
=====================================
// Dispather
https://developer.android.com/kotlin/coroutines/coroutines-adv?hl=ko&#main-safety
->
코루틴 사용에 사용될 스레드를 결정한다.
Dispatchers.Main : 기본 main스레드, ui와 상호작용, suspend함수 호출 및 ui작업, livedata를 업데이트.
Dispatchers.IO : 디스크 또는 네트워크 io작업, room라이브러리 사용등에 최적화
Dispatchers.Default : 메인스레드 외부에서 cpu사용이 많은 작업을 할 때, 예로 리스트 정렬 또는 json파싱
=====================================
// withContext(Dispatchers.??)
->
이것 또한 정지함수이다.
코루틴의 실행을 Dispathcer.?? 스레드로 옮긴다.
withContext내부에 작성된 코드를 특정 context의 스레드로 옮기기 위해 사용하는듯.
코틀린 공홈에 api쪽을 보니까, 코루틴 컨텍스트를 옮겨서 해당 블록을 실행하고, 완료될때까지 suspend한다고 한다.
스레드 전환에 대해서 오버헤드가 적고 나름 최적화가 있다는거 같은데, 정확하게 무슨말인지 이해안됨. (https://developer.android.com/kotlin/coroutines/coroutines-adv?hl=ko#perf)
또 이게 같은 블록내에 있는 코드들도 다른 스레드 작동과 함께 일어날 수 있어서 정확하게 보장이 안된다는듯?
// 일반적으로 suspend함수는 main스레드에서 작동함,
따라서 main-safety하기 위해서 withContext를 사용해야한다.
=====================================
/*
코루틴 시작
*/
- launch : 새 코루틴을 시작하고, 호출자에게 결과를 반환하지 않음.
- async : 새 코루틴을 시작하고 await라는 정지함수로 결과를 반환하도록 한다.
보통의 일반함수는 awit를 호출할 수 없다.
일반 함수에서 새 코루틴을 시작하고, 코루틴 내부에서만 사용하거나, 정지함수내에서 병렬분해(parallel decomposition)를 실행할때 사용한다.
launch와 async는 예외를 다르게 처리한다.
async는 await호출을 예상하므로 예외를 보유한다, await호출의 일부로 예외를 다시 발생시킨다.
async를 사용하여 일반함수에서 새 코루틴을 시작하는 경우, 예외를 자동으로 삭제할 수 있다.
이렇게 삭제된 예외는 비정상 종료에 나타나지 않거나, logcat에 기록되지 않는다.
아래 문서를 참고.
(https://medium.com/androiddevelopers/cancellation-in-coroutines-aa6b90163629)
// 병렬 분해 (Parallel decomposition)
https://developer.android.com/kotlin/coroutines/coroutines-adv?hl=ko&#parallel
suspend함수는 리턴될때, 모든 코루틴이 종료되야한다.?
그래서 리턴전에 코루틴이 종료되는것을 보장하는게 필요하다?
그래서 "coroutineScope" 를 정의한다.
코루틴이 완료되도록 보장할 수 있게한다.
async - awit or anync - awaitAll로 작업이 모두 완료되도록 보장한다.?
coroutineScope 빌더는 모든 코루틴들이 완료 될때까지,
해당 함수를 호출했었던 코루틴을 재개하지 않는다?
coroutineScope는 코루틴이 발생시키는 예외를 호출자에게 다시 보낸다?
자세한거는 아래 문서 참고.
https://kotlinlang.org/docs/composing-suspending-functions.html
=====================================
/*
코루틴 개념
*/
// CoroutineScope
launch 또는 async를 사용하여 만든 코루틴을 추적한다.
실행중인 코루틴은 scope.cancel()을 호출하여 취소할 수 있다.
안드로이드에서는
ViewModel에는 viewModelScope가 있고 (https://medium.com/androiddevelopers/easy-coroutines-in-android-viewmodelscope-25bffb605471)
Lifecycle에는 lifecycleScope가 있다.
디스패처와 달리, coroutineScope는 코루틴을 실행하지 않는다. (범위를 제한하는 용도인듯, 이 범위내에서 코루틴들을 시작하게 되는거고)
취소되면 코루틴을 만들 수 없다.
수명주기를 제어하는 클래스가 제거되는 경우에, scope.cancel()을 호출해야한다.
viewModelScope는 자동으로 취소하게 되어있음.
---------------
// Job
코루틴의 핸들,
launch 또는 async로 만드는 코루틴을 각각 고유하게 식별하고, 수명주기를 관리하는 Job인스턴스를 반환.
--------------
// CoroutineContext
다음 요소들을 사용하여, 코루틴의 동작을 정의한다.
- Job: 코루틴의 수명주기를 제어
(https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/)
- CoroutineDispatcher : 적절한 스레드에 작업을 전달
- CoroutineName : 디버깅에 유용한 코틀린의 이름
- CoroutineExceptionHandler : 포착되지 않은 예외(uncaught exceptions)를 처리.
(https://medium.com/androiddevelopers/exceptions-in-coroutines-ce8da1ec060c)
coroutineScope를 만들때, 넣은것들을 상속받게되는데, launch나 async함수에 다시 전달하여 재정의 할 수 있다.
Job은 전달해도 효과가 없다, job의 새인스턴스가 항상 새코루틴에 할당되기 때문에. -> 이게 맞는말인가??? 이해안댐
=====================================
// 코루틴 테스트
runTest {}
: TestScope를 만든다.
: 테스트용으로 만들어진 코루틴 빌더
테스트당 runTest를 한번 호출해야하고, 표현식 본문을 사용하는게 좋음.
코루틴의 지연을 자동으로 건너뛰게 한다.
최상위 코루틴외에, 새 코루틴을 만들때는 적절한 TestDispatcher를 선택해야한다.
다른 디스패처로 이동하게 되면, 지연을 건너뛰지 않게되고, 테스트코드가 여러 스레드에서 실행되므로 예측하기 어려워진다.
그래서 테스트에서 실제 디스패처를 교체하려면 테스트 디스패처를 삽입해야함.
// TestDispatchers
TestDispatchers는 테스트용 CoroutineDispatcher를 구현하는것이다.
TestDispatcher에는 2가지가 있다.
- StandardTestDispatcher
- UnconfinedTestDispatcher
둘 다 TestCoroutineScheduler를 사용하여, 가상시간을 제어하고, 테스트 내에서 실행중인 코루틴을 관리한다.
스케쥴러 인스턴스는 하나만 있어야하고, TestDispatcher간에 공유해야한다.
runTest는 TestScope를 만든다.
TestScope는 기본적으로 StardardTestDispatcher이다.
runTest는 TestScope의 디스패처에서 사용하는 스케쥴러의 대기열에 추가되는 코루틴을 추적하고,
이 스케줄러에 대기중인 작업이 있는한 반환하지 않는다.
//
- StandardTestDispatcher
새 코루틴이 시작되면 코루틴이 기본 스케쥴러의 대기열에 추가되어 테스트 스레드를 사용할 수 있을때마다 실행된다.
테스트 스레드가 최상위 테스트 코루틴을 실행하는 동안 생성되지 않으면,
모든 새코루틴은 테스트 코루틴이 완료된 후에만 실행된다? (뭔소리일까? 정확하게 모르겠다.)
(runTest가 리턴되기 직전까지 새로운 코루틴들이 안돌아간다는 말인가?? 인거 같은데..)
대기열에 추가된 코루틴이 실행 되도록 테스트 코루틴을 생성하는 방법에는 여러가지가 있다.
이런 모든 호출은 반환하기전에 테스트스레드에서 다른 코루틴이 실행되게 한다.
- advanceUntilIdle
대기열에 남은 항목이 없을 때까지 스케줄러에서 다른 코루틴을 모두 실행합니다.
이는 대기 중인 코루틴이 모두 실행되도록 하는 좋은 기본 선택이며 대부분의 테스트 시나리오에서 작동합니다.
- advanceTimeBy
주어진 양만큼 가상 시간을 진행하고 가상 시간의 해당 지점 전에 실행되도록 예약된 코루틴을 실행합니다.
- runCurrent
현재 가상 시간에 예약된 코루틴을 실행합니다.
//
- UnconfinedTestDispatcher
코루틴 빌더가 반환될때까지 기다리지 않고, 새 코루틴들이 바로 시작됨.
빠르게 코루틴이 시작되지만
그렇다고, 완료될때까지 빠르게 되는건 아니다.
// 테스트 디스패처 삽입
// Main 디스패처 설정
로컬 유닛 테스트에서는 Main디스패처를 사용할 수 없다.
Dispatchers.setMain랑 MainDispatcherRule을 쓴다고 한다.
// 테스트 외부에서 디스패처 만들기
뭔소리인지 잘모르겠다.
Test클래스 내부에 또 디스패처 넣어야하는 객체가 있으면, 테스트 디스패처 만들어서 주입시킨다는듯?
// 자체 TestScope 만들기
// 범위 삽입 Injecting a scope
비슷비슷한 내용같다..
근데 뭔소리인지 잘모르겠음.
=====================================
코루틴 권장사항
// 디스패처 삽입
새 코루틴을 만들거나, withContext를 호출 할 때, Dispatcher를 하드코딩 하지 말 것.
-> 생성자주입으로 만들라는 말이다.
// 정지 함수는 기본 스레드에서 호출하기전에 안전해야함.
-> 디스패처를 미리미리 옮겨둬라는 말인듯?
// ViewModel은 코루틴을 만들어야 함
- 뷰모델에서는 코루틴을 시작하고, 정지함수는 다른곳에
// 변경 가능한 유형 노출하지 않음
- private val 을 써라는 말인듯.
// 데이터 및 비즈니스 레이어는 정지 함수와 흐름을 노출해야 함
// 비즈니스 및 데이터 레이어에서 코루틴 만들기
실행한 작업이 화면에 있을때만 관련이 있으면, 호출자의 수명주기를 따라야함.
coroutineScope나 supervisorScope
externalScope
-> 아직은 정확히 뭔말인지 모르겠다.
호출자따르려면, coroutineScope나 supervisorScope를 쓰고,
(코루틴 스코프는 자식이 실패하면 이 범위실패, 나머지모두도 실패.
슈퍼바이저스코프는, 자식이 실패하면, 그 실패한 자식만 실패, 범위자체 실패는 모든 하위와함께 실패하지만, 상위가 취소되지는 않음.)
호출자 상관없이 작업완료되려면 externalScope를 써라는 말인듯?
// 테스트에 TestDispatcher 삽입
// 코루틴을 취소 가능하게 만들기
큰 작업을 하기 전에 취소되었는지 확인하는 아래 함수를 넣어라.
ensureActive() // Check for cancellation
// 예외에 주의
viewModelScope 또는 lifecycleScope에서 예외를 잡아라
=====================================
// 추가 리소스
https://developer.android.com/kotlin/coroutines/additional-resources?hl=ko
안드로이드 공식홈, 코틀린 공식홈을 다 보고나면 이 페이지의 링크를 하나씩 찾아봐야겠네
심심할때 유튜브 보는게 아니라 여기있는거 하나씩 봐야함..ㅜ
중간중간에 코드랩이 있으니, 코드랩을 해봐야함.
=====================================
여기서부터는 Flow!!!!!!!!
여기서부터는 Flow!!!!!!!!
// 개요
https://developer.android.com/kotlin/flow?hl=ko
정지함수는 값을 순차적으로 보낼 수 있는거와 달리,
흐름은 비동기로 계산 할 수 있는 데이터 스트림의 개념이다.
흐름은 정지함수를 사용하여 비동기로 처리할 수 있다.
데이터 스트림에는 다음과 같은 3가지가 있다.
- 생산자 (Consumer) : 스트림에 추가되는 데이터를 생산, 비동기로 생산될 수 있음. ex. 레포지토리
- 중개자 (Intermediary) : 중개자에서 스트림에 내보내는 값이나 스트림을 수정할 수 있음.
- 소비자 (Producer) : 스트림 값을 소비함. ex. ui레이어
// 흐름 만들기
flow 빌더를 사용,
emit함수를 사용해서 새값을 수동으로 데이터 스트림에 보낼 수 있다.
flow빌더는 코루틴 내에서 실행된다.
비동기의 이점이 있지만, 제약이 생긴다
- 순차적 sequential 이다, 정지함수를 호출하게되면 정지함수가 끝날때까지 기다린다.
- 흐름빌더 내부에서 다른 CoroutineContext의 값을 emt하면 안된다, 이런 경우 callbackFlow를 사용.
// 스트림 수정
중개자가 중간 연산자 intermediate operators 를 이용하여 값을 소비하지 않고 데이터 스트림을 수정 할 수 있다.
.map, .onEach같은 메서드들이 중간 연산자라는거였다.
// 흐름에서 수집
터미널 연산자 terminal operator 를 사용하여,
값 수신 대기를 시작하는 흐름을 트리거한다.
스트림의 모든값을 가져오려면, collect를 사용
collect는 정지함수이므로, 코루틴에서 실행되어야한다.
흐름 collect가 중지 될 수 있다.
- 수집된 코루틴이 취소된 경우, 기본 생산자도 중지된다.
- 생산자가 emit을 완료한 경우, 데이터 스트림이 종료되고, collect를 호출한 코루틴이 실행을 다시 시작한다.
흐름은 콜드 및 지연이다.
흐름에서 터미널 연산자가 호출 될 때 마다 생산자 코드가 실행된다.
여러 소비자가 동시에 수집 할 때, 흐름을 최적화하고 공유하려면 shareIn연산자를 사용.
// 예상치 못한 예외 포착
중간 연산자인 catch를 사용
catch에서 emit을 할 수도 있다.
// 다른 CoroutineContext에서 실행하기
flowOn을 사용하면 된다는데,
flowOn을 뒤에 쓰는데, 업스트림에 반영된다는게 이해가 안됨.
그냥 그렇게 되나보다 하고 써야겠다.
// Jetpack 라이브러리의 Flow
// 콜백 기반 API를 흐름으로 변환
콜백 기반 api를 흐름으로 변환하는게 callbackFlow빌더이다.
flow빌더와 달리,
send함수를 이용해서 다른 coroutineContext로 보내거나
offer함수로 코루틴 외부로 값을 내보낸다.
기본 용량은 64개,
send는 공간이 생길때까지 생산자를 정지.
offer는 채널에 요소를 추가하지 않고, 즉시 false를 반환.
=====================================
// 흐름 테스트하기
여기는 어차피 이해가 제대로 안될테니,
빠르게 훑어야겠다.
테스트 방법은 흐름을 입력으로 쓰는지, 출력으로 쓰는지에 따라서 다르다.
// 모조 생산자 만들기
// 테스트에서 흐름 내보내기 항목 어설션하기
// 테스트 중 연속 수집
// Turbine 사용하기
// StateFlow 테스트
// stateIn으로 생성된 StateFlow로 작업하기
=====================================
// StateFlow 및 SharedFlow
// StateFlow
현재 상태와 새로운 상태 업데이트를 collect에 내보내는 관찰 가능한 상태홀더 흐름이다.
value속성을 통해서도 현재 상태값을 읽을 수 있다.
collect를 해도 생산자 코드가 트리거 되지 않음.
StateFlow는 항상 활성 상태이고, 메모리내에 있다.
참조가 없는 경우에만 가비지 컬렉션되고.
// StateFlow, Flow, LiveData
StateFlow는 LiveData와 비슷하지만, 다음과 같은 다르게 작동한다.
- StateFlow는 초기상태를 생성자에 전달해야하지만, liveData는 그렇지 않다.
- 뷰가 STOPPED상태가 되면 LiveData.observe()는 소비자를 자동으로 등록 취소하는 반면,
StateFlow는 다른 흐름에서 수집하는 경우 자동으로 수집을 중지하지 않음.
동일한 동작을 하려면, Lifecyle.repeatOnLifecycle블록에서 흐름을 수집해야한다.
// shareIn을 사용하여 콜드 흐름을 핫 흐름으로 만들기
-> 이 문서는 설명이 잘못된거 같은데, StateFlow로 얘기를 시작할게 아닌거 같고, 소스 스니펫도 타입이 이상한거 같다.
shareIn으로 콜드 플로를 핫플로로 변경할 수 있다.
다음 3가지를 전달해야한다.
- A CoroutineScope that is used to share the flow. This scope should live longer than any consumer to keep the shared flow alive as long as needed.
- The number of items to replay to each new collector.
- The start behavior policy.
// SharedFlow
모든 소비자에게 값을 내보내는 핫흐름
SharedFlow는 StateFlow의 유연한 구성일반화이다. (State보다 더 일반적으로 쓸 수 있다는 말인듯)
SharedFlow에 맞춤동작? 아귀먼트를 넣어서 옵션으로 정하는게 있는듯?
- replay 이전에 보낸 여러값들 다시 보낸다?
- onBufferOverflow 버퍼가 전송할 항목이 가득찬 경우 적용할 정책.
기본은 BufferOverflow.SUSPEND. 다른 옵션은 DROP_LATEST 또는 DROP_OLDEST입니다.
=====================================
여기서 부터
코틀린 공식홈
// Coroutines guide
https://kotlinlang.org/docs/coroutines-guide.html
개요 설명이라 딱히 내용이 없음
=====================================
// Coroutines basics
https://kotlinlang.org/docs/coroutines-basics.html
// Your first coroutine
일시중단 가능함, 스레드를 옮겨다닐 수 있다.
launch:
코루틴 빌더이다.
새 코루틴을 시작한다.
delay:
중지함수이다.
특정시간 코루틴을 일시중지한다.
기본스레드가 차단되지는 않음.
다른 코루틴이 기본스레드를 실행할 수 있음
runBlocking:
이것도 코루틴 빌더이다.
비 코루틴을 코루틴코드와 연결해주는 브릿지이다.
runBlocking을 빼먹으면 에러가 발생한다, launch에서, launch는 오직 코루틴 스코프에서만 선언되므로.
runBlocking이 수행이 완료되기까지, 이를 실행하는 스레드를 차단한다.
// Structured concurrency
코루틴 스코프내에서 실행되서, 문제 발생을 줄인다는 내용인듯.
// Extract function refactoring
launch내부에 작성될 내용을 중지함수로 분리하는 방법을 알려줌.
정지함수는 일반함수처럼 코루틴 내부에서 사용될 수 있지만,
정지함수의 추가적인 기능은 코루틴의 실행을 정지할 수 있다는 점이 있음.
// Scope builder
coroutineScope빌더를 사용해서, 다른 빌더내에서 다른 스코프를 선언 할 수 있다.
실행된 자식이 완료되기 전까지 완료되지 않게된다.
runBlocking과 coroutineScope가 비슷하게 보인다,
차이점은
runBlocking은 대기를 위해 현재 스레드를 차단한다,
coroutineScope는 일시중단하고, 다른 사용을 위해 현재 스레드를 릴리즈시킨다.
이러한 차이점으로, runBlocking은 일반함수이고, coroutineScope는 중지함수이다.
(이거 이해가 제대로 안됨, 외우는게 답이겠는데?, 아! 기본스레드를 계속 잡고 있게 되니까 따로 중지되는애가 없다는 말인듯?)
(이래서, runBlocking을 최상위, main함수에만 붙인다는 말인가보다)
// Scope builder and concurrency
이거는 이해 잘됨.
launch를 2개 시키고,
다른 코루틴 스코프에서 완료될때를 기다리면서 진행된다는 말인듯.
// An explicit job
launch 코루틴 빌더는 Job객체를 리턴하는데,
이 잡은 실행된 코루틴을 명시적으로 다룰 수 있는거다.
join()을 쓰면 그 코루틴이 완료될때까지 기다린다.
// Coroutines are light-weight
코루틴은 JVM스레드보다 가볍다.
JVM스레드에서 메모리 많이먹는걸 코루틴으로 변경하면 메모리 적게 쓸 수 있다.
=====================================
// 코루틴과 채널 - 튜토리얼
https://kotlinlang.org/docs/coroutines-and-channels.html
작업1을 봐야하는데,
무진장 어렵다...........ㅜㅜ
쉬고 집에가서 해야겠다.
나름 정독했는데, 대부분 잘모르겠고..
특히나 채널, 테스트쪽은 뭔소리인지 잘모르겠다........
=====================================
// 취소 및 시간초과
// Cancelling coroutine execution
launch함수에서 받은 job으로,
취소할 수 있다.
// Cancellation is cooperative
코루틴은 전부 취소할 수 있는데,
계산중인거는 취소못함.
계산중인거 취소하려면 JobCancellationException이 발생함.
// Making computation code cancellable
취소를 확인하는 함수를 쓰거나,
yeild를 사용
예제로는 확장 프로퍼티인, isActive로 무한루프를 체크하는걸 보여줌.
// Closing resources with finally
그냥 finally구문이나 use블록으로 자원을 닫아라는 내용?
취소가능한 일시중단 함수는 왜 일반적으로 처리하는 취소시 CancellationException을 일으킨다는거지?
// Run non-cancellable block
-> 뭔소리지? 취소된 코루틴에서 중지를 필요로 할때가 무슨말인가..
대충 finally에 중지함수 들어가야하면,
withContext(NonCancellable)가 필요하다는 말인듯?
// Timeout
withTimeout(1300L)
withTimeoutOrNull
// Asynchronous timeout and resources
이것도 뭔말인지 모르겠다.
=====================================
// Composing suspending functions
// Sequential by default
일반적으로 순차적으로 작동함
// Concurrent using async
비동기로 동시성 작업하려면
launch나 aync로 실행해야하는데,
예제에서 두개 같이 받아야하니까 aync를 썼다.
// Lazily started async
지연시작을 하려면, await를 하거나,
job.start를 해야한다.
// Async-style functions
GlobalScope.async로 함수를 비동기스타일로 만들지 마라는듯?
뭔말인지 정확하게 모르겠음
// Structured concurrency with async
coroutineScope를 써서 구조화한다, 자식이 취소되면 부모에게까지 간다는데..
=====================================
// Coroutine context and dispatchers
// Dispatchers and threads
- 그냥 launch : Coroutine context를 상속받음
- Dispatcher.Unconfined : 호출자스레드(여기 예시에서는 메인스레드)에서 돌지만 약간 다른 뭔가가 있다.
- Dispatcher.Default : 스코프내에서 다른 디스패처가 명시적으로 있지 않을때 사용됨, 스레드의 백그라운드풀을 공유한다.
- newSingleThreadContext :
내가 새로 만드는 context, 코루틴을 쓰기위해 다른 스레드를 만드는데 값비싸다,
close를 해서 닫아야하고, 최상위변수나 어플리케이션 전체에서 써야한다.
// Unconfined vs confined dispatcher
Dispatcher.Unconfined 는 호출자 스레드에서 코루틴을 시작하는데, 첫번째 중단지점까지만 작동함.
한번 정지한 후에는 정지함수에 의해 결정된 다른 스레드에서 다시 재개된다.
이거는 cpu시간을 소비하거나, 공유데이터를 업데이트하지 않을때 사용하면 된다.
반면 디스패처는 기본적으로 외부 CoroutineScope에서 상속받는데,
호출자 스레드로 제한되므로, FIFO로 예측 가능
Unconfined Dispatcher는 고급 매커니즘이다,
일부 작업을 즉시수행하고, 나중에 실행하는 작업들을 위해,
일반적인 코드에서는 사용하지 말 것.
// Debugging coroutines and threads
코루틴은 한 스레드에서 중지하고, 다른 스레드에서 재개될 수 있다.
그래서 특별한 도구가 없으면 디버깅이 어려울 수 있음.
디버깅은 kotlinx-coroutines-core 1.3.8 이상 버전에서 작동함.
- IDEA를 이용해서 디버깅
- 로깅을 이욯해서 디버깅
이거를 JVM옵션으로 넣어줘야한다. ( -Dkotlinx.coroutines.debug )
-ea 옵션을 넣으면 디버깅모드도 켜진다고한다.
// Jumping between threads
newSingleThreadContext에서 use를 사용해서 자원해제를 한다.
runBlocking으로 context를 명시하고,
withContext로 context를 전환시킴
// Job in the context
Job는 context의 부분이고,
coroutineContext[Job]으로 접근할 수 있다.
유사하게 isActivie는 사실 다음과 같은 구문이다.
coroutineContext[Job]?.isActive == true.
// Children of a coroutine
CoroutineScope 에서 코루틴이 시작되면,
그것의 CoroutineScope.coroutineContext을 상속받고,
새 코루틴의 Job은 부모 코루틴job의 자식이 된다.
부모 코루틴이 취소되면 그것의 자식또한 취소된다.
그러나 부모-자식 관계는 두가지 방법으로 명시적으로 무시할 수 있다.
- 코루틴을 시작 할 때 명시적으로 다른 스코프에서 시작하는거 (ex. GlobalScope.lauch)
- 다른 Job객체가 새 코루틴의 Context로 전달되는 경우
// Parental responsibilities
부모는 항상 자식코루틴의 완료를 기다린다.
부모는 명시적으로 추적하거나, Job.join을 해서 기다릴 필요가 없다.
( 여기서 Join하는거는 마지막 println을 나중에 찍기위해서 한 듯.)
// Naming coroutines for debugging
CoroutineName을 사용해서 따로 코루틴이름을 지정할 수 있다.
// Combining context elements
코루틴 컨텍스트를 연결하려면, +연산자를 사용하면 된다.
// Coroutine scope
다른 수명주기가 있는 어플리케이션 객체가 없어질때 코루틴도 종료되게 하기위해 CoroutineScope를 사용한다.
CoroutineScope와 MainScope를 함수로 만들 수 있다.
전자는 범용이고, 후자는 ui를 위한 스코프이고 Dispatcher.Main을 기본 디스패처로 사용한다.
// Thread-local data
스레드 로컬데이터를 코루틴과 코루틴간에 전달하는 기능.
DefaultDispatcher로 실행해서 스레드가 변경되게 yield()를 해도, threadLocal.asContextElement를 쓰면 값을 공유하고 있는다는 거 같은데
완벽하게 이해하지는 못했다.
threadLocal값은 제약이 있는데, 이 값이 변경되면 안된다는듯. withContext를 사용해서 업데이트해야한다는듯. 문서링크가 있다.
class Counter라는걸 대안으로 사용할 수도 있다.
ThreadContextElement라는거도 있다.
=====================================
// Asynchronous Flow
이 문서.. 양이 엄청난데..?
중지함수는 싱글 값만 리턴한다.
여러값을 리턴하려면 Flow를 써야함.
// Representing multiple values
- Sequences
- Suspending functions
- Flows
// Flows are cold
flow는 collect하기전까지 작동하지 않음. (이게 suspend로 flow를 리턴하는 함수를 마킹하지 않는 이유이다.)
// Flow cancellation basics
코루틴의 취소하는것과 따라간다.
// Flow builders
flow { } 빌더 말고도, 다른 선언이 있다.
flowOf 로 고정된 값을 정의 할 수 있다.
.asFlow()로 컬렉션이나, 시퀀스의 값을 변환할 수 있다.
// Intermediate flow operators
업스트림에 대해서 작업을하는 연산자들, 이것들도 콜드이고 정지함수가 아니다.
이 연산자 블록내에 정지함수를 넣어서 장기실행되는걸 요청 할 수 있음.
// Transform operator
transform { }
// Size-limiting operators
.take(2)
// Terminal flow operators
이거는 흐름을 수집하는 suspend함수이다.
collect
toList
toSet
first
reduce
fold
// Flows are sequential
// Flow context
flow collect는 항상 호출한 코루틴에서 발생한다.
따라서, flow빌더도 collect의 context를 따르게 된다.
// A common pitfall when using withContext
flow는 context preservation해야하므로,
context를 변경해서는 안된다.
// flowOn operator
context를 변경해야하면, flowOn연산자를 사용한다.
emit, collect하는 코루틴 따로 만들어지고 스레드도 다르다는거 같은데? 이해 잘 안됨.
// Buffering
buffer()가 collect를 다 기다리지않고, 바로 emit을 해버린다는듯.
emit이 느린거를 해결할 수 있다는거 같음.
// Conflation (융합)
중간 처리는 필요없고 결과가 필요하면
conflate()를 사용한다,
collector가 느릴때 사용함
Conflation은 중간 값을 버린다.
// Processing the latest value
collect가 느리면, collect되는걸 취소하고 새값을 받게 하는거.
// Composing multiple flows
// Zip
2개의 flow를 합칠 수 있다.
// Combine
zip하고 합친다는거에서는 비슷하지만, 각각의 emit된 최신값으로 다시 계산을 한다는듯?
// Flattening flows
Flow<Flow<String>>이 되는걸 Flow로 만들기 위해서 어떻게 하는가를 설명하는듯?
// flatMapConcat
내부흐름이 완료될때까지 기다린다?
// flatMapMerge
들어올 flow를 한번에 수집하고, 뒤에 들어오는거랑 합친다는듯
// flatMapLatest
새거가 호출되면 예전꺼는 취소되는거
// Flow exceptions
// Collector try and catch
// Everything is caught
collector 쪽에 try-catch를 하면, emitter, intermediate, terminal 쪽의 에러를 모두 잡는다.
// Exception transparency
emitter내부에 try-catch를 써서 투명하게 예외처리
emitter는 try-catch문대신 catch 오퍼레이터를 쓸 수 있다.
// Transparent catch
catch연산자는 업스트림만 예외로 잡는다
// Catching declaratively
업스트림만 잡으니까, collect쪽에서 예외 안나게 하려고,
onEach를 catch위에 써서 예외를 잡는 예제를 알려줌.
// Flow completion
flow완료 후 작업실행하게 하기,
명령형, 선언형 2가지가 있다.
// Imperative finally block (명령형 finally 블록)
// Declarative handling
onCompletion 중간연산자를 호출하는 방법
이것의 이점은 throwable을 파라미터로 받아서, 예외가 일어났었는지 확인가능함.
onCompletion 은 예외를 처리하지는 않고 예외가 다운스트림으로 흐름
추가 onCompletion이나, catch로 예외를 다룰 수 있다는듯?
// Successful completion
catch는 예외 뜨면 작업을 안하지만?
이거는 모든 예외를 볼 수 있다는듯? (업스트림이 성공한 경우만 예외를 수신한다..?)
// Imperative versus declarative
명령형대 선언형
어떤 방식이 더 선호되는가? -> 자신의 선호와 코드 스타일로..
// Launching flow
collect() 는 흐름 수집 될 때까지 기다리는데,
launchIn() 는 별로의 코루틴에서 실행되서, 다음코드가 바로 실행될 수 있다.
launchIn()은 흐름 코루틴만 취소 할 수 있는 job을 반환한다고 함.
// Flow cancellation checks
flow빌더는 추가적인 ensureActive체크를 해서
루프에서도 바로 취소 할 수 있다.
특정 플로우 연산자는 성능을 이유로 중간에 취소를 확인하지 않는다고함.
(runBlocking에서 반환되기전에 취소를 감지한다.)
// Making busy flow cancellable
.cancellable() 를 넣어서 위의 케이스 같은걸 중간에 취소되게 할 수 있다.
// Flow and Reactive Streams
Flow가
Reactive Streams,
RxJava,
Reactor같은거에 영향을 받고 비슷하게 만들어졌다.
그리고 변환하는 라이브러리를 제공한다고 함.
=====================================
// Channels
https://kotlinlang.org/docs/channels.html
Deferred values는 코루틴간에 단일 값을 제공하고,
채널은 값의 stream을 전달하는 방법을 제공한다.
// Channel basics
BlockingQueue와 비슷한데,
blocking하는 put대신에 suspending하는 send,
blocking하는 take대신에 suspending하는 receive를 사용한다.
// Closing and iteration over channels
큐와 다르게, 더 이상 입력이 없으면 닫을 수 있다.
channel.close()는 결과적으로 close토큰을 보내는 것이다.
리시버 쪽에서 계속 반복하다가 close토큰을 받으면 끝이나고, 이전에 보낸거를 다 받을 수 있다.
// Building channel producers
produce 블록을 사용해서 채널을 리턴하고, consumeEach로 받아낼 수 있다.
(producer부분을 함수로 만들고 결과를 리턴하는것 처럼 보이게하려고..? 한다는듯? )
// Pipelines
뭔가 .. 설명이 별로다.
producing한거를, 다시 소비하면서 처리하는거를 말하는거 같은데?
파이프 라인은 하나의 코루틴에서 값의 스트림을 가능한 무한대로 만드는 패턴이다.
// Prime numbers with pipeline
파이프라인사용의 극단적인 예시
iterator를 사용해서 만들 수 있다는듯?
하지만, 파이프라인의 이점은 Dispather.Default에서 멀티코어로 채널을 사용할 수 있다는 것.
sequence/iterator를 빌트인할 수 없다는 제약이 있다,
왜냐면 sequence/iterator는 중지함수를 쓸 수 없어서..
// Fan-out
여러 코루틴이 같은 채널을 리시브 하면서, 작업을 분배 할 수 있다.
// Fan-in
여러 코루틴이 동일한 채널로 보낼 수 있다.
// Buffered channels
버퍼링 되지 않은 채널은 sender와 receiver가 만날때 요소가 전송된다. (랑데뷰 채널)
sender가 먼저호출되면, receiver가 호출되기를 기다리고,
receiver가 먼저 호출되면, sender가 호출되기를 기다림.
채널 팩토리와 프로듀스 빌더는 버퍼 사이즈를 구체화하기위해서 capatity파라미터를 가진다.
버퍼는 일시중지전에 여러 요소를 보낼 수 있게한다. (버퍼가 차면 중지됨)
용량이 4이면, sender가 5번 log가 찍힘.
5일때 멈추기 때문에.
// Channels are fair
코루틴이 여러개 있는데, 채널을 하나 쓰면,
그 여러개의 코루틴이 번갈아서 동작된다는듯?
// Ticker channels
틱커 채널은 특별한 랑데뷰 채널이다.
채널에서 마지막 소비이후 일정 딜레이마다 Unit을 만드는 채널임.
=====================================
// Coroutine exceptions handling
https://kotlinlang.org/docs/exception-handling.html
// Exception propagation
2가지 방법이 있는데,
launch와 actor는 자동으로 전파되고,
async와 produce는 사용자에게 노출한다.
이러한 빌더(launch랑 actor말하는듯?)가
다른 코루틴의 자식이 아닌,
루트 코루틴에서 사용될때,
그 이전 빌더는 그 예외를 포착하지 않는다.
반면에 후자는 마지막 예외를 소비하는 사용자에 의존된다.
예를 들어, await, receive
예시로 GlobalScope를 만드는게 루트코루틴?을 만든다는거 같은데..?
제대로 모르겠음.
// CoroutineExceptionHandler
uncaught예외를 콘솔에서 프린트하는 행동을 커스터마이징하는게 가능한데,
CoroutineExceptionHandler가 일반적은 catch block을 사용하는 것 처럼
루트 코루틴과 자식을 처리하는데 사용된다.
CoroutineExceptionHandler에서 예외를 복수 할 수 없다.
그 코루틴을 이미 완료된다, 핸들러가 호출되었때.
일반적으로 핸들러는 예외를 로그하고,
에러메세지를 표시하고 프로그램을 종료하거나 다시 시작하는데 사용된다.
CoroutineExceptionHandler 는
오직 uncauch예외(다른 방식으로 처리되지 않은 예외)에서만 호출된다.
특히 모든 자식 코루틴(다른 작업의 컨텍스트에서 생성된 코루틴???)은
예외처리를 부모 코루틴에 위임하고,
그것 또한 다시 부모에게 위임해서 루트까지 간다.
그래서 그 컨텍스트에 있는 CoroutineExceptionHandler는
결코 사용되지 않는다.
추가적으로,
async빌더는 늘 모든 예외를 잡는다, 그리고 deferred 객체결과에서 그것들을 나타낸다.
그래서 그것의 CoroutineExceptionHandler도 역시 영향이 없다.
supervision 스코프에서는 그들의 부모에 전파하지 않는다고 함.
이 규칙들에 대해서 제외된다함.
-> 이것 역시 정확하게 뭔소리인지 모르겠고 감으로 알겠다..
// Cancellation and exceptions
취소는 예외와 밀접하게 관계있다.
코루틴 내부적으로 취소를 위해서 CancellationException을 사용하는데,
이 예외는 모든 핸들러를 무시한다, 그래서 그것들은 부가적인 디버그 정보로서 사용해야한다.
그리고 그것은 catch블록에 의해 처리된다.
Job.cancel을 사용하여 코루틴이 취소 될때,
그것은 끝나지만, 그것의 부모는 취소되지 않는다.
만약 CancellationException말고 다른 예외를 만나면
코루틴을 그 예외로 부모를 취소시킨다.
이 행동은 재정의 할 수 없고,
그리고 사용된다 구조화된 동시성에 대해서 안정적인 코루틴 계층을 제공하기위해,
CoroutineExceptionHandler 구현은 자식 코루틴을 위해서 사용되지 않는다.
이 예에서,
CoroutineExceptionHandler는 globalScope에 설치되는데,
runBlocking의 스코프에는 이치에 맞지 않다,
메인 코루틴은 늘 자식코루틴이 설치된 핸들러가 있어도 예외로 완료되고 취소되기 때문에
그 오리지널 예외는 핸들된다 부모에 의해,
모든 그것의 자식이 종료될때까지
그리고 그것은 다음 예에서 보여진다.
예시가
두번째 자식에서 취소되면서,
부모까지 취소되고 첫번째 자식이 취소되게 된다는 예인듯?
// Exceptions aggregation
예외 집계
코루틴의 다양한 자식이 예외로 취소될때,
일반적인 규칙은 첫예외가 이긴다이다.
그래서 첫번째 예외가 핸들된다.
모든 부가적인 예외는 첫번째 예외 이후에, suppressed (억압된) 것들로 첫번째 예외에 추가된다.
suppressed 예외는 jdk7이상에서만 지원된다.
이 기능은 자바버전에서만 지원되고, js및 네이티브는 제한적일 수 있음.
취소 예외는 투명하게, 그리고 기본적으로 언래핑 된다.
// Supervision
우리가 이전에 공부했다시피, 취소는 양방향 관계전파이다, 코루틴의 전체 계층을 통한
취소가 요구되는 단방향 케이스를 보자.
이러한 예의 좋은 케이스는 그것의 스코프에서 정의된 job을 쓰는 UI컴포넌트이다.
만약 UI의 자식이 실패를 하면, 그것은 플 전체 컴포넌트를 취소할 필요가 없다.
그러나 만약 UI컴포넌트가 파괴되면 (그리고 그 잡이 취소되고),
그때 그것은 그 결과가 더이상 필요없으면 모든 자식 job이 취소되는게 필요하다.
다른 예는 서버 프로세스이다, 다양한 자식 job을 소비하는, 그리고 필요하다 supervise하는 것이,
그들의 예외를, 그들의 실패를 트래킹하고, 그리고 실패된 것을 재시작하기 위한.
// Supervision job
supervision job은 이런 목적을 위해 사용 될 수 있다.
그것은 일반 job과 유사하다, 그 취소예외가 오직 밑으로 전파되는.
이것은 다음예제로 쉽게 보인다.
-> 예제를 봐도 잘모르겠는데...
supervision.cancel()을 하면, 원하는 자식을 죽일 수 있다는 건가..?
// Supervision scope
범위가 지정된 동시성을 위해서 coroutineScope대신에
supervisorScope를 사용할 수 있다.
그것은 단방향으로 취소를 전파하고, 취소한다 만약 그것이 스스로 실패한 모든 자식을.
그것은 또한 기다린다 모든 자식의 완료를 coroutineScope처럼
(이게 취소되면 부모까지 전파되는게 아니라 그냥 취소된 자식들만 죽이는 용이라는거네?)
예시가 자식 만들고 그 스코프에서도 예외를 하나 만드는데,
자식이 죽고 끝나기 기다린다음에 바깥 catch까지 가게 된다는듯?
// Exceptions in supervised coroutines
다른 중요한 일반적인거랑 수퍼바이저 잡의 차이는 예외를 핸들링하는 것이다.
모든 자식은 예외처리 매커니즘을 통해서 자체적으로 예외를 핸들해야한다. (수퍼바이저에서 이렇게 처리해야한다는듯)
이 차이는 자식의 실패가 부모로 전파되지 않기 때문이다.
supervisorScope내에서 실행된 코루틴은
그들의 스코프에서 설치된 CoroutineExceptionHandler 를 사용해야된다는 것을 의미한다.
, 루트 코루틴에서 한 것과 마찬가지로
아.. 어렵다
=====================================
// Shared mutable state and concurrency
https://kotlinlang.org/docs/shared-mutable-state-and-concurrency.html
공유된 가변 상태와 동시성
코루틴은 다중 스레드 디스패처, Dispatchers.Default같은걸 사용해서 병행 실행 될 수 있다.
그것은 모든 일반적인 병행문제를 나타낸다.
그 주요 문제는 공유 가변 상태에 접근에 동기화 되는것의 문제이다.
코루틴 내에서 이 문제에 대한 솔루션은 멀티 스레드 세계의 솔루션과 유사하다,
그러나 다른 특별한게 있다.
// The problem
100개의 스레드를 만드는데 counter를 1씩 증가시키게함.
근데 counter가 늘 100,000이 찍히길 원하는데 그렇지 않다.
동기화 없이 실행 했기 때문이다.
// Volatiles are of no help
volatiles(휘발성은) 도움이 되지 않는다.
volatile변수로 만들면 동시성문제가 해결된다는 오해가 있는데 그렇지 않다.
예제를 보자.
이 코드는 느리게 작동하지만,
늘 10만을 얻지 못한다,
volatile변수는 보장한다 linearizable(원자에 대한 기술 용어이다.)한 읽기와 쓰기를
대응하는 변수에, 그러나 큰 액션의 원상성을 보장하지 않는다. (무슨말인지 정확히 모르겠다.)
// Thread-safe data structures
일반적인 솔루션은, 스레드와 코루틴에서 작동할,
스레드 세이프 데이터 구조(ex. synchronized, linearzable, atomic)를 사용하는 것이다,
제공하는 모든 필요한 동기호를, 작동의 일치를 위해, 공유되는 state를 수행하기 위해 필요한.
간단한 카운터의 예에서 우리는 AtomicInteger 클래스를 사용 할 수 있다, 그리고 그것은
atomic incrementAndGet 오퍼레이션을 가진다.
이것은 특정 문제에 대해서 빠른 솔루션이다.
그것은 일반 카운터, 컬렉션, 큐, 그리고 다른 일반 데이터구조와 일반 오퍼레이션에서 작동한다.
그러나 그것은 쉽게 스케일 하지 않는다,
복잡한 스테이트나 복잡한 오퍼레이션으로, 스레드 세이프할 사용준비가 되지 않은..
// Thread confinement fine-grained
세분화된 스레드 제한
스레드 제한은
특정 공유 상태에 대한 모든 엑세스가 단일 스레드로 제한 되는,
공유된 가변 상태 문제에 대한 접근이다,
그것은 일반적으로 UI어플리케이션에서 사용되고, 모든 UI상태는 싱글 이벤트 디스패처/어플리케이션 스레드로 제한된다.
그것은 쉽게 적용할 수 있다, 싱글 스레드 컨텍스트를 사용해서
이 코드는 매우 느리게 작동한다, 세분화된 스레드 제한 때문에.
각각 개별적인 증가는 바뀐다, 멀티 스래드인 Dispatchers.Default 컨텍스트로부터
withContext블록에서 싱글 스레드된 컨텍스트로 사용하기 위해서
// Thread confinement coarse-grained
거친 스레드 제한
실제로 스레드 제한은 큰 청크에서 수행된다,
예로 큰 부분, 스테이트 업데이팅 비스니스 로직의,
제한된다, 싱글 스레드로
그것은 다음 예제와 같다, 싱글르래드된 컨택스트에서 각각 코루틴이 시작된다.
이제 훨씬 빨리 수행되고, 바른 결과를 만든다.
-> 왜 이런 차이가 생기는건지.. 모르겠다..
fine-grained는 Dispatcher.Default에서
멀티스레딩하려해서 컨택스트 스위칭이 생겨서 더 느리게 되는건가.?
// Mutual exclusion
상호 배제
뮤택스 익스클로션은 결코 동시적으로 수행되지 않을 크리티컬 센션으로
공유된 상태의 수정을 보호하는 것이다.
차단 세계에서 일반적으로 synchronized , ReentrantLock 를 쓴다
코루틴의 대안은 Mutex라 불린다.
그것은 lock가 unlock함수로 크리티컬 섹션을 구분짓는다.
주요 차이점은 Mutex.lock()은 정지함수라는 것이다.
스레드를 차단하지 않는다.
이것은 또한
mutex.lock();
try{
..
}
finally {
mutax.unlock()
}
을 편리하게 표현할 withLock확장이 있다.
이 예제는 세분화한 락킹이고, 그래서 그것의 값어치가 크다.
그러나 그것은 좋은 선택이다. 몇몇 상황에서, 너가 절대적으로 공유한 상태를 주기적으로 수정해야할 때,
그러나 이 상태가 제한되는 자연스러는 스레드는 없다? (이거는 또 무슨소리인가 모르겠다.)
// Actors
액터는 코루틴 조합으로 구성된 엔티티이다,
코루틴으로 제한되고 캡슐화된 상태, 그리고 채널이다, 다른 코루틴과 통신하기 위한.
간단한 액터를 함수로 작성 할 수 있다.
그러나 복잡한 상태를 가진 액터가 클래스를 위해서 더 적합하다.
액터의 메일박스 채널을
해당 범위로 편리하게 결합해서 메세지를 수신하고
송신채널을 결과 작업 개체로 결합하는 액터 코루틴 빌더가 있다,
액터에 대한 단일 참조를 그것의 핸들로서 사용할 수 있다.
액터 사용의 첫단계는 메세지의 클래스를 정의하는 것이다, 액터가 프로세스하기 위한 메세지를 클래스를.
코틀린의 봉인된 클래스는 이런 목적에 적합하다.
CounterMsg 봉인된 클래스를 정의한다,
IncCounter와, 카운터를 증가하기 위해서,
그리고 getCounter메세지를 그것의 값에서 얻기위해
CompletableDeferred communication primitive는
미래에 알려진 싱글 값을 나타내기위한,
그 목적을 위해서 여기 사용된다.
그리고 우리는 함수를 정의한다, 액터 코루틴 빌더를 사용하는 액터 실행하는 함수를.
예제를 봤는데..
이해가 안간다. 잘모르겠다.
액터 자체가 실행되는 컨택스트는 중요하지 않다.
액터는 코루틴이고 코루틴은 순차적으로 실행되고,
그래서 상태의 제한, 특정 코루틴에서 제한하는것은 작동한다, 공유 가변 상태의 문제를 해결로서
실제로 액터는 자신의 private상태를 수정할 수 있다,
그러나 메세지를 통해서만 서로에게 영향을 줄 수 있다, (lock이 필요한 것을 피한다.)
액터는 부하상태에서 락킹하는거보다 효율적이다,
이 케이스에서 늘 작동한다, 그리고 컨택스트를 전환할 필요가 없기때문에
=====================================
// Select expression (experimental)
https://kotlinlang.org/docs/select-expression.html
select expression은 정지함수를 동시에 await하게 할 수 있고,
이용가능한 첫번째를 select한다.
// Selecting from channels
receive 중지함수는 한 채널에서 수신할 수 있는데,
select 블록을 사용해서 onReceive를 하면, 두가지 모두에서 동시에 수신할 수 있다.
// Selecting on close
채널을 닫을때, select에 onReceive절에서 실패해서,
그에 따른 select에서 예외가 발생한다.
onReceiveCatching절을 사용해서 특정액션을 수행할 수 있다, 채널이 닫힐때
그 예제는 다음과 같다, select는 select절의 결과를 리턴하는 표현식이다.
특이하게 작동하는데
첫번째는
select는 첫번째꺼에 편향되고,
첫번째꺼가 때때로 중지될때 b가 보낼 기회가 주어진다.
두번째로 채널이 닫히면, onReceiveCating이 즉시 선택된다.
// Selecting to send
select expression은 selection의 편향된 특성과 결합된 좋은 사용을 할 수 있는
onSend절이 있다.
기본 채널에서 consumer가 따라갈 수 없을 때,
값을 사이드 채널로 보내는 정수 producer의 예를 보자.
// Selecting deferred values
deffered value는 onAwait절을 사용해서 selected 될 수 있다.
랜덤 딜레이 후, deferred string을 리턴하는 async함수를 시작해보자.
이제 main함수는 첫번째 기능이 완료되기를 기다리고,
여전히 활성 상태인 deferred value의 수를 계산한다.
우리는 사용했다는 사실을 알아라, select expression은 kotlin DSL이다,
그래서 우리는 임의의 코드를 사용해서 이에 대한 절을 제공할 수 있다.
이 경우 우리는 반복한다, 각 지연 값에 대해서 onAwait절을 제공하기위해
지연값의 목록을 반복한다.
// Switch over a channel of deferred values
지연된 값의 채널 전환
써보자,
지연된 스트링 값의 채널을 소비하는,
각 지연된 값에 대해서 기다리는,
채널 producer 함수를,
그러나 오직 다음 지연값이 오기 기다리거나 채널이 닫힐 때 까지만 가능한.
이 예는 동일한 select에서 onReceiveCatching과 onAwait을 함께 넣는다.
그것을 테스트하기 위해서,
우리는 사용할 거다,
특정 시간 이후에 구체적인 값으로 해결되는
간단한 비동기 함수를,
main 함수는 단지 시작한다 코루틴을 프린트하기위해서,
switchMapDeferred의 결과를, 그리고 그것에 몇몇 테스트 테이터를 보내기 위해서
//
어휴 무슨 소리인지 모르겠다.
결론)
- 문서봤던거 이미 기억안남.
- 코루틴을 단번에 학습할 수 없다.
- 간단하게 프로젝트에 적용해 나가는게 필요함.
앞으로 할 것)
- 리싸이클러뷰 best practice로 만드는 연습
- 개인프로젝트
'Android > Kotlin in android' 카테고리의 다른 글
안드로이드 버튼 android:src와 app:srcCompat 두 속성의 차이점 (with. chatgpt) (0) | 2024.02.23 |
---|---|
android android:gravity와 android:layout_gravity의 차이 (0) | 2023.08.25 |
RecyclerView를 연습해보았다 (공부한 자료 & 회고) (0) | 2023.04.28 |
뷰 바인딩 (View Binding) (0) | 2022.10.27 |
힐트 hilt 공부 방법, 용어 메모, ide에서 아이콘설명 (개인공부) (0) | 2022.10.15 |