반응형
[Kotlin] 코 루틴 Coroutine 사용법 및 개념 정리 -Join, repeat, Cancel,withTimeout - 3편
전 포스트에서는 runblocking을 다뤄보았는데요 join, repeat을 쓰기 위해서 runBlocking을 아셔야 하기 때문에 모르신다면 보고 오시는 것을 추천합니다.
[Kotlin] 코루틴 Coroutine 사용법 - runBlocking - 2편
Join, Repeat 사용법
- join은 job.join이 끝날 때까지 현재 코루틴에게 기다리라고 명령하는 함수입니다.
- Join은 suspend에서만 작동합니다. suspend는코루틴이 멈춰있을 때를 의미합니다. runBlocking은 suspend상태이므로 Join은 runblocking에서 사용 가능하게 됩니다.
- GlobalScope을 val로 만들어줌으로써 join함수를 runblocking에서 쓸 수 있게 되었습니다.
class MainActivity : AppCompatActivity() {
val TAG = "MainActivity"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val job = GlobalScope.launch(Dispatchers.Default) {
repeat(5){ //5번 반복하게됩니다.
Log.d(TAG,"Coroutine is still working...")
delay(1000L)
}
}
runBlocking {
job.join() //suspend에서만 작동하기때문에 runblocking안에 넣어줍니다.
//join() 함수가 끝날때까지 runblocking이 지속 됩니다.
Log.d(TAG,"Main thread is continuing...")
}
Log.d(TAG,"Done")
}
- 위 코드를 실행시키고 로그를 확인해보면 로그:Coroutine is still working... 이 1초 간격으로 5번 반복되고, 로그:Main thread is continuing... 가 나오게 됩니다. 그 후 Done이 나옵니다.
- Done이 마지막으로 나온 이유는 runBlocking이 Main UI 스레드를 중지 시켰기때문에 runblocking이 끝나고 로그 Done이 실행되게 됩니다. 만약 runBlocking에서 job.join()이 없었다면 job이 끝날 때까지 mainUI 스레드를 중지시키는 join이 없기 때문에, 반복이 끝나기 전에 done이 나오게 됩니다.
runBlocking에 join이있을때 log: 로그 Done의 위치를 비교해주세요.
runBlocking에 join없을때 log
Cancel 사용법
job.cancel()
- cancel을 사용하게되면 현재 진행 중인 코루틴을 멈출 수 있습니다.
- 활용 예시입니다.
- 이예 시에서는 job(coroutine의 value 이름)의 dispather을 default로 써주면서 runblocking이 되고 5번 반복하도록 했습니다.
//dispathcer가 default이기때문에 runblocking이 실행되도 멈추지않습니다.
//runblocking은 main쓰레드만 멈춥니다.
val job = GlobalScope.launch(Dispatchers.Default) {
repeat(5){ //it will repeat 5 times
Log.d(TAG,"Coroutine is still working...")
delay(1000L)
}
}
runBlocking {
delay(2000L) //2초 delay
job.cancel()// 2초 딜레이후 job을 멈추게합니다.
Log.d(TAG,"Main thread is continuing...")
}
- Job은 1초에 한번 반복하고 delay는 2초 후 job을 끝내기 때문에, 로그를 확인해보면 로그: Coroutine is still working... 이 두 번 반복된 후에, job.cancel()이 실행되고 job이 종료되면서 로그:Main thread is continuing... 이 나오게 됩니다.
Cancel 사용 시 문제점
하지만 cancel()을 쓰실 때 유의하실 점이 있습니다. 위 예제는 5번 반복될 때마다 1초 delay가 있어서 cancel이 되었을 때 coroutine에게 cancel이 되었다고 전달할 시간이 충분합니다. 만약 coroutine안에 계산하는데 복잡하고 오래 걸리는 작업이 있다면 그걸 처리하느라 cancel이 되었다는 정보를 corutine의 계산이 끝날 때까지 못 들을 수 있습니다. 예시를 보여드리겠습니다.
- fib는 계산시간이 오래 걸리게 하도록 만든 함수입니다.
- globalScope이 default이기 때문에 runblocking에 delay가 들어가도 멈추지 않습니다.
class MainActivity : AppCompatActivity() {
val TAG = "MainActivity"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val job = GlobalScope.launch(Dispatchers.Default) {
Log.d(TAG,"Starting long running calculation")
for(i in 30..40){
Log.d(TAG, "Result for i = $i : ${fib(i)}")
}
Log.d(TAG, "Ending Long running calculation")
}
runBlocking {
delay(2000L)
job.cancel()
Log.d(TAG,"job canceled")
}
}
fun fib(n:Int): Long{
return if(n==0) 0
else if(n ==1) 1
else fib(n - 1)+ fib (n-2)
}
}
- 실행했을 때 로그 결과입니다.
- 보면 job은 35 이후로 캔슬 됐지만("job canceled") job이 40까지 계속 계산중인 것을 볼 수 있습니다. 그리고 모두 계산 후 coroutine이 끝난 걸 볼 수 있습니다. (로그:"ending Long running calculation")
- 이렇게 cancel 은 coroutine이 바쁘게 되면 캔슬하라는 명령을 전달받지 못하는 상황이 오게 됩니다.
Cancel 문제점 해결법
이문제를 어떻게 해결해야 할까요? 먼저 우리는 coroutine이 활성화돼있는지 확인할 필요가 있습니다.
- isActive 사용하기
val job = GlobalScope.launch(Dispatchers.Default) {
Log.d(TAG, "Starting long running calculation")
for (i in 30..40) {
//추가시작
if (isActive) {
Log.d(TAG, "Result for i = $i : ${fib(i)}")
}
//추가끝
}
Log.d(TAG, "Ending Long running calculation")
}
- withTimeout 사용하기
- withTimeout은 안에 있는 함수가 정해진 시간보다 오래 걸릴 경우 cancel 시키는 함수입니다.
val job = GlobalScope.launch(Dispatchers.Default) {
Log.d(TAG, "Starting long running calculation")
withTimeout(3000L) {
for (i in 30..40) {
if (isActive) {
Log.d(TAG, "Result for i = $i : ${fib(i)}")
}
}
}
Log.d(TAG, "Ending Long running calculation")
}
runBlocking {
delay(2000L)
job.cancel()
Log.d(TAG, "job canceled")
}
- withTimeout이 설정되었기 때문에 아래 있는 for Loop가 3초 이상 걸릴 경우 취소되게 하였습니다.
참고자료:
다음 포스트에서는 async와 await, lifecycleScope과 ViewmodelScope을 다뤄보겠습니다.
반응형