티스토리 뷰
728x90
코루틴
- suspend 함수
suspend fun sum(val1: Int, val2: Int): Int {
delay(1000)
return val1 + val2
}
- 함수 실행을 중단한 다음, 나중에 필요할 때 다시 실행을 계속 진행할 수 있는 함수
- 함수에 suspend라는 키워드를 붙인다.
- 스레드를 block 시키지 않고 일시 중단하며, 그동안 스레드가 다른 작업을 수행할 수 있다.
sum(val1: Int, val2: Int, val3: Continuation)
- 컴파일 시 마지막 파라미터로 Continuation 객체를 추가한다.
- Continuation을 통해 중단(suspend) 시점의 실행 정보들을 저장하고, 실행이 다시 재개(resume)되면 저장한 정보를 기반으로 실행을 다시 이어나간다.
suspend fun main() {
println("Task started")
delay(100)
println("Task finished")
}
- 일반 함수는 suspend 함수를 호출할 수 없고, suspend 함수나 코루틴 안에서 호출할 수 있다.
- → main()을 suspend 함수로 만들면 호출 가능
2. 코루틴 빌더
- suspend 함수를 생성하지 않고도 코루틴 빌더를 통해 일시 중단하고 재개할 수 있는 함수를 호출할 수 있다.
- launch()
fun main() {
GlobalScope.launch {
delay(100)
println("Task finished")
}
Thread.sleep(200)
}
-
- CoroutineScope.() -> Unit 타입의 suspend 람다를 인자로 받는다. (새 코루틴의 본문이 됨)
- 코루틴을 시작하고 실행중인 작업의 상태를 추적/변경할 수 있는 Job 인스턴스를 반환한다.
- async()
suspend fun main() {
val msg1 = GlobalScope.async {
delay(100)
"Hello "
}
val msg2 = GlobalScope.async {
delay(100)
"World"
}
val result = msg1.await() + msg2.await()
println(result) // Hello World
}
-
- await()를 통해 계산 결과를 얻을 수 있는 Deferred 인스턴스를 반환한다.
- await()는 해당 코루틴이 완료될 때까지 기다린다.
- runBlocking()
fun main() {
GlobalScope.launch {
println("Background task: ${Thread.currentThread().name}") // DefaultDispatcher-worker-1
}
runBlocking {
println("Primary task: ${Thread.currentThread().name}") // main
}
}
-
- 현재 스레드에서 실행되는 코루틴을 생성한다.
- launch(), async()는 스레드 풀 사용
- 코루틴의 본문이 완료될 때까지 현재 스레드의 실행을 block 시킨다.
- 블러킹 동작 때문에 다른 코루틴 안에서 사용할 수 없고 최상위에서만 사용해야 한다.
- 현재 스레드에서 실행되는 코루틴을 생성한다.
3. structed concurrency
- 코루틴끼리의 부모-자식 관계로 코루틴 실행 시간을 제한할 수 있다.
- 어떤 코루틴을 다른 코루틴에서 실행한다면, 상위 코루틴이 부모/하위 코루틴이 자식이 된다.
- 자식의 실행이 모두 끝나야 부모가 끝난다.
fun main() {
runBlocking { // 부모 코루틴
println("Parent task started")
launch { // 자식 코루틴
println("Task 1 started")
delay(200)
println("Task 1 finished")
}
launch { // 자식 코루틴
println("Task 2 started")
delay(200)
println("Task 2 finished")
}
delay(100)
println("Parent task finished")
}
println("Shutting down")
}
실행 결과
Parent task started
Task 1 started
Task 2 started
Parent task finished
Task 1 finished
Task 2 finished
Shutting down
|
- 부모 코루틴이 자식 코루틴보다 일찍 끝나지만, 자식이 끝날 때까지 일시 중단 상태로 기다린다.
- runBlocking()이 메인 스레드를 block하고 있었기 때문에 부모 스레드가 끝난 후 마지막 메시지가 출력된다
4. context
fun main() {
GlobalScope.launch {
val job = coroutineContext[Job.Key] // 현재 잡 얻기
println("Task is active: ${job?.isActive}") // 잡의 상태 출력
}
}
- 모든 코루틴은 CoroutineContext 인터페이스의 context 안에서 실행된다.
- 코루틴 본문 안에서 coroutineContext 프로퍼티를 통해 context에 접근할 수 있다.
- context는 키-값 쌍으로 이루어진 불변 컬렉션이다.
- Element들은 각 Key를 기반으로 CoroutineContext에 등록된다.
- Element → CoroutineId, CoroutineName, CoroutineDispatcher, ContinuationInterceptor, CoroutineExceptionHandler
- CoroutineContext 함수
operator fun <E : Element> get(key: Key<E>): E?
fun <R> fold(initial: R, operation: (R, Element) -> R): R
operator fun plus(context: CoroutineContext): CoroutineContext
fun minusKey(key: Key<*>): CoroutineContext
-
- get() : 주어진 key에 해당하는 context element를 반환한다.
- fold() : context의 element들에 특정한 함수를 수행하여 결과를 반환한다.
- plus() : 현재 context와 인자로 주어진 다른 context의 요소를 모두 포함하는 context를 반환한다. 즉, context끼리 합쳐주는 역할을 한다.
- minusKey() : 현재 context에서 인자로 주어진 key를 제외한 새로운 context를 반환한다.
- CoroutineContext 구현체
- EmptyCoroutineContext: context가 명시되지 않을 경우 default로 사용되는 context
- CombinedContext: 두개 이상의 context가 명시될 경우 context를 연결해준다.
- Element: context의 각 element들
- 코루틴 빌더를 통해 인자로 어떤 context를 넘기는지에 따라 context의 상태가 달라진다.
- CoroutineContext에 plus operator 함수가 있기 때문에, 각 element들을 +를 통해서 연결할 수 있다.
- CombinedContext를 통해 Element끼리 묶어 하나의 CoroutineContext가 된다.
fun CoroutineScope.showName() {
println(coroutineContext[CoroutineName]?.name)
}
fun main() {
runBlocking {
showName() // null
launch() {
showName() // null (현재 context 이어받음)
}
launch(coroutineContext + CoroutineName("Worker")) { // 생성한 context을 코루틴에 넘김
showName() // Worker
}
}
}
- 코루틴 빌더에 의해 만들어진 코루틴은 현재 context를 이어받는다.
- 빌더 함수에 context 파라미터를 지정해서 새 context를 넘길 수 있다.
- withContext()
fun CoroutineScope.printName() = println(Thread.currentThread().name)
fun main() {
newSingleThreadContext("Worker").use { worker ->
runBlocking {
printName() // main
withContext(worker) { printName() } // Worker
printName() // main
}
}
}
-
- context를 전환하여 코루틴을 수행할 수 있게 해준다.
5. CoroutineScope
interface CoroutineScope {
val coroutineContext: CoroutineContext
}
- CoroutineContext 하나만 가지고 있는 인터페이스
- 모든 코루틴 빌더는 CoroutineScope의 확장 함수다.
- 새로운 코루틴의 범위를 지정한다.
class MyActivity : AppCompatActivity(), CoroutineScope {
lateinit var job: Job
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
job = Job()
}
override fun onDestroy() {
super.onDestroy()
job.cancel()
}
fun loadDataFromUI() = launch {
val ioData = async(Dispatchers.IO) { ... }
val data = ioData.await()
draw(data)
}
}
- CoroutineScope를 상속받고 coroutineContext를 override한다.
- 클래스 내에서 만든 코루틴은 메인 스레드와 Job 객체를 context로 갖는다.
- GlobalScope
object GlobalScope : CoroutineScope {
override val coroutineContext: CoroutineContext
get() = EmptyCoroutineContext
}
-
- EmptyCoroutineContext를 context로 가지고 있다.
- 애플리케이션의 생명주기 동안 작동하는 코루틴을 만들 수 있다.
- coroutineScope()
fun main() {
runBlocking {
println("Parent task started")
coroutineScope {
launch {
println("Task 1 started")
delay(200)
println("Task 1 finished")
}
launch {
println("Task 2 started")
delay(200)
println("Task 2 finished")
}
}
println("Parent task finished")
}
}
실행 결과
Parent task started
Task 1 started
Task 2 started
Task 1 finished
Task 2 finished
Parent task finished
|
-
- 사용자가 직접 코루틴의 스코프를 지정할 수 있다.
- runBlocking()과 마찬가지로 람다의 결과를 반환하고, 자식들이 완료되기 전까지 기다린다.
- coroutineScope()는 suspend 함수이기 때문에 현재 스레드를 block 시키지 않는다.
- 자식 코루틴 실행이 끝날 때까지 corouineScope()가 일시 중단되기 때문에 마지막 문장이 가장 마지막에 출력된다.
728x90
'app > kotlin' 카테고리의 다른 글
[kotlin/코틀린] 코루틴 동시성 통신 (채널, 프로듀서, 티커, 액터, 플로우) (0) | 2023.07.20 |
---|---|
[kotlin/코틀린] 코루틴 흐름 제어 (Job, 타임아웃, 디스패처, 예외 처리) (0) | 2023.07.20 |
[kotlin/코틀린] 위임 프로퍼티 (0) | 2023.07.19 |
[kotlin/코틀린] 연산자 오버로딩 (0) | 2023.07.19 |
[kotlin/코틀린] 리플렉션 (0) | 2023.07.19 |