본문 바로가기
프로그래밍 놀이터/Kotlin, Coroutine

Coroutine 과 놀아보기 #2

by 돼지왕 왕돼지 2019. 5. 23.
반응형

 Coroutine 과 놀아보기 #2


ascontextelement, cancellationexception, channel capacity, channel suspending, coroutine threadlocal, Coroutine 과 놀아보기 #2, coroutinescope, job cancel, RendeZvous, runBlocking, runblocking cancellationexception, ThreadLocal, threadlocal context, TimeoutCancellationException, withTimeout, withTimeoutOrNull


-

Coroutine 과 놀아보기 #1 과 마찬가지로 공부를 하면서 의아스러운 부분들을 테스트 코드를 통해 검증하며 정리한 글입니다.



-

Job 이 cancel 되었을 때는 CancellationException 이 발생한다.

하지만, 이는 정상 동작으로 간주하여 coroutine 내에서는 crash 가 나진 않는다.

( runBlocking 안에서는 crash 가 난다.. runBlocking 은 따로 공부할 필요가 있다. )



-

withTimeout 의 경우는 일단 synchronous 한 동작을 한다. (withContext 와 비슷하다.)

아래와 같은 코드가 있다면 어떤 로그도 찍히지 않는다.

withTimeout(1000L){
  delay(1500L)
  log("after delay")
}
log("afterTimeout")



-

withTimeout 의 경우는 시간 내에 coroutine 을 완료하지 않으면, TimeoutCancellationException 이 발생한다.

runBlocking 안에서 수행할 경우에는 exception 이 throw 되고, 그 외 coroutineScope 에서는 CancellationException 의 sub class 로서 exception 이 소비된다.

(runBlocking 에 대해서는 더 공부해보아야 할 필요가 있다)



-

async 는 launch 처럼 불리는 순간 로직이 수행된다.

await 나 start 등의 action 이 있을 때만 수행하게 하려면,, CoroutineStart.LAZY 값을 주면 된다.



-

ThreadLocal 쪽이 정확히 이해가 안 되어 이런 실험을 해보았다.

val threadLocal = ThreadLocal<String?>()

runBlocking{
    val job = launch{
        logCurrentThread(“launch1”)
        log("threadLocal1 = ${threadLocal.get()}”)
        threadLocal.set(“1”)
        
        launch{
            logCurrentThread(“launch2”)
            log("threadLocal2 = ${threadLocal.get()}”)
            threadLocal.set(“2”)            

            launch{
                logCurrentThread(“launch3”)
                log("threadLocal3 = ${threadLocal.get()}”)
                threadLocal.set(“3”)            
            }
        }
    }
    job.join()
    log(“mainThread threadLocal = ${threadLocal.get()}”)
}


결과는?

당연히 모두 main thread 이니 null, 1, 2, 3 이 순차적으로 찍힌다.


그렇다면? 모두 Default dispatcher 를 지정하면 어떻게 될까?


DefaultDispatcher-worker-1 by launch 1

threadLocal1 = null

DefaultDispatcher-worker-2 by launch 2

threadLocal2 = null

DefaultDispatcher-worker-2 by launch 3

threadLocal3 = 2

main threadLocal = null


원래 threadLocal 동작이 아는 데로 동작한다.


그렇다면... 왜 threadLocal 을 element 로 지정하는 것은 어떤 효과가 있어 소개가 된 것일까?

Dispatcher 에 + operator 로 threadLocal context (asContextElement) 를 더해본다.

그렇다면..


DefaultDispatcher-worker-1 by launch 1

threadLocal1 = null

DefaultDispatcher-worker-2 by launch 2

threadLocal2 = 1

DefaultDispatcher-worker-2 by launch 3

threadLocal3 = 2

main threadLocal = null


asContextElement 가 현재의 threadLocal 값을 기본으로 전달하기 때문에 전달받은 값을 사용하여 출력이 된다.

그리고 마지막 main thread 에서는 변경되지 않은 null 값을 참조한다.

결국 threadLocal 을 element 로 넘기면 thread 가 달라도 해당 thread 에 값을 넘긴다는 의미만 있는 것 같은데..

어떻게 써야 할지는 조금 더 고민해볼 필요가 있을 것 같다.



-

Channel 의 suspending 이 어떻게 되는지 궁금하여 다음과 같이 실험해보았다.

runBlocking{
    val channel = Channel<Int>()
    launch{
        for(x in 1..5){
            val value = x * x
            log(“calculation $value”)
            channel.send(value)
            log(“channel sent $value”)
        }
    }

    repeat(5){
        log(“repeat $i”)
        log(“channel received = “${channel.receive()}”)
    }
}

결과는..

repeat 0

calculation 1

channel sent 1

calculation 4

channel received 1

repeat 1

channel received 4

repeat2

channel sent 4

calculation 9

channel sent 9

calculation 16

channel received 9

repeat 3

channel received 16

repeat 4

channel sent 16

calculation 25

channel sent 25

channel received 25


실제 순서의 보장이 어느 정도 의미있을지는 모르겠지만...

send 와 receive 사이의 suspending 의 resume timing 이.. 2개단위라는게 까리하다…


그래서 알아보니 Channel 의 capacity 기본값은 0 인 RENDEZVOUS 값이다.

이것이 영향이 있는것 같은데.. 이 녀석들의 정체를 정확히 모르겠다..



-

runBlocking 대신 GlobalScope.launch(Dispatchers.Main) 으로 하면…

MainThread 를 block 하지 않는다…





성과


1. ThreadLocal 은 어디에 쓸지 실무에서 써봐야 알겠다. asContextElement 로 전달하면 set 하는 값들이 적용되진 않는다.

2. Channel 의 send 와 receive 는 동작상의 문제는 없어보이지만, 정확히 어떤 mechanism 으로 resume 되는지 확실치 않다. capability 가 영향이 있을 것 같긴 한데..

3. runBlocking 대신 GlobalScope.launch(Dispatchers.Main) 을 사용하면.. Main thread 를 block 하지 않는다!!!

4. 별로 성과가 없지만.. runBlocking 에 대해 따로 공부해야겠다는 것이 가장 큰 성과라고 할 수 있겠다.




반응형

댓글