[Coroutine] 코루틴(3)

Janzizu·2022년 9월 11일
0
post-thumbnail

Job 취소

import kotlinx.coroutines.*

suspend fun doOneTwoThree() = coroutineScope {
    val job1 = launch {
        println("launch1: ${Thread.currentThread().name}")
        delay(1000L)
        println("3!")
    }
  
    
    val job2 = launch {
        println("lanch2: ${Thread.currentThread().name}")
        println("1!")
    }
    
   
    val job3 = launch {
        println("launch3: ${Thread.currentThread().name}")
        println("2!")
    }  
   
   	delay(800L)
    job1.cancel()
    job2.cancel()
    job3.cancel()
    println("4!")
}

fun main() = runBlocking {
    doOneTwoThree()
    println("runBlocking: ${Thread.currentThread().name}")
    println("5!")
}

launch1: main @coroutine#2
lanch2: main @coroutine#3
1!
launch3: main @coroutine#4
2!
4!
runBlocking: main @coroutine#1
5!

명시적인 Job에 대해 cancel 메서드를 호출해 취소할 수 있다.

취소 불가능한 Job

import kotlinx.coroutines.*

suspend fun doCount() = coroutineScope {
    val job1 = launch(Dispatchers.Default) {
        var i = 1
        var nextTime = System.currentTimeMillis() + 100L
        
        while(i <= 10) {
            val currentTime = System.currentTimeMillis()
            if(currentTime >= nextTime) {
                println(i)
                nextTime = currentTime + 100L
                i++```
            }
        }
    }
    
    delay(200L)
    job1.cancel()
    println("doCount Done!")
}

fun main() = runBlocking {
    doCount()
}

1
2
doCount Done!
3
4
5
6
7
8
9
10

취소든 종료든 끝난 이후에 doCountDone을 출력하고 싶었지만 취소가 되지 않은 코드이다.

cancel & join

import kotlinx.coroutines.*

suspend fun doCount() = coroutineScope {
    val job1 = launch(Dispatchers.Default) {
        var i = 1
        var nextTime = System.currentTimeMillis() + 100L
        
        while(i <= 10) {
            val currentTime = System.currentTimeMillis()
            if(currentTime >= nextTime) {
                println(i)
                nextTime = currentTime + 100L
                i++
            }
        }
    }
    
    delay(200L)
    job1.cancel()
    job1.join()
    println("doCount Done!")
}

fun main() = runBlocking {
    doCount()
}

1
2
3
4
5
6
7
8
9
10
doCount Done!

cancel 이후에 join을 넣어서 취소가 된 이후에 출력되게끔 하였다.

cancelAndJoin

import kotlinx.coroutines.*

suspend fun doCount() = coroutineScope {
    val job1 = launch(Dispatchers.Default) {
        var i = 1
        var nextTime = System.currentTimeMillis() + 100L
        
        while(i <= 10) {
            val currentTime = System.currentTimeMillis()
            if(currentTime >= nextTime) {
                println(i)
                nextTime = currentTime + 100L
                i++
            }
        }
    }
    
    delay(200L)
   	job1.cancelAndJoin()
    println("doCount Done!")
}

fun main() = runBlocking {
    doCount()
}

1
2
3
4
5
6
7
8
9
10
doCount Done!

cancel을 하고나서 join을 하는 일이 자주 있기 때문에 한 번에 할 수 있게끔
cancelAndJoin을 쓰면 된다.

cancel이 가능한 코루틴

import kotlinx.coroutines.*

suspend fun doCount() = coroutineScope {
    val job1 = launch(Dispatchers.Default) {
        var i = 1
        var nextTime = System.currentTimeMillis() + 100L
        
        while(i <= 10 && isActive) {
            val currentTime = System.currentTimeMillis()
            if(currentTime >= nextTime) {
                println(i)
                nextTime = currentTime + 100L
                i++
            }
        }
    }
    
    delay(200L)
   	job1.cancelAndJoin()
    println("doCount Done!")
}

fun main() = runBlocking {
    doCount()
}

1
doCount Done!

isActive를 호출하면 해당 코루틴이 활성화상태인지 알 수 있다.

finally

import kotlinx.coroutines.*

suspend fun doOneTwoThree() = coroutineScope {
    val job1 = launch {
        try {
           println("launch1 :${Thread.currentThread().name}")
           delay(1000L)
           println("3!")
        } finally {
            println("job1 is finishing")
        }
    }
        
        val job2 = launch {
            try {
                println("launch2: ${Thread.currentThread().name}}")
                delay(1000L)
                println("1!")
            }finally {
                println("job2 is finishing")
            }
        }
        
        val job3 = launch {
            try {
                println("launch3: ${Thread.currentThread().name}")
                delay(1000L)
                println("2!")
            }finally {
                println("job3 is finishing")
            }
        }
        
        delay(800L)
        job1.cancel()
        job2.cancel()
        job3.cancel()
        println("4!")
  }
  

fun main() = runBlocking {
    doOneTwoThree()
    println("runBlocking: ${Thread.currentThread().name}")
    println("5!")
}

launch1 :main @coroutine#2
launch2: main @coroutine#3}
launch3: main @coroutine#4
4!
job1 is finishing
job2 is finishing
job3 is finishing
runBlocking: main @coroutine#1
5!_

suspend 함수들은 JobCancellationException을 발생하기 때문에 try catch finally로 대응할 수 있다.

취소 불가능하게

import kotlinx.coroutines.*

suspend fun doOneTwoThree() = coroutineScope {
    val job1 = launch {
        withContext(NonCancellable) {
           println("launch1 :${Thread.currentThread().name}")
           delay(1000L)
           println("3!")
        }
        delay(1000L)
        print("job1: end")
    }
        
        val job2 = launch {
           withContext(NonCancellable) {
                println("launch2: ${Thread.currentThread().name}}")
                delay(1000L)
                println("1!")
           }
           delay(1000L)
           print("job2: end")
        }
        
        val job3 = launch {
           withContext(NonCancellable) {
                println("launch3: ${Thread.currentThread().name}")
                delay(1000L)
                println("2!")
           }
           delay(1000L)
           print("job3: end")
        }
        
        delay(800L)
        job1.cancel()
        job2.cancel()
        job3.cancel()
        println("4!")
  }
  

fun main() = runBlocking {
    doOneTwoThree()
    println("runBlocking: ${Thread.currentThread().name}")
    println("5!")
}

launch1 :main @coroutine#2
launch2: main @coroutine#3}
launch3: main @coroutine#4
4!
3!
1!
2!
runBlocking: main @coroutine#1
5!

withContext(NonCancellable)을 이용하면 취소 불가능한 블록을 만들 수 있다.
withContext블록 밖에 있는 jobx: end 구문은 취소되어 출력되지 않는다.

타임 아웃

import kotlinx.coroutines.*

suspend fun doCount() = coroutineScope {
    val job1 = launch(Dispatchers.Default) {
        var i = 1
        var nextTime = System.currentTimeMillis() + 100L
        
        while(i <= 10 && isActive) {
            val currentTime = System.currentTimeMillis()
            if(currentTime >= nextTime) {
                println(i)
                nextTime = currentTime + 100L
                i++
            }
        }
    }
}

fun main() = runBlocking {
    withTimeout(500L) {
        doCount()
    }
    
}

1
2
3
4
Exception in thread "main" kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 500 ms
at (Coroutine boundary. (:-1)
at FileKtmain$1$1.invokeSuspend (File.kt:-1) at FileKtmain1.invokeSuspend(File.kt:1)Causedby:kotlinx.coroutines.TimeoutCancellationException:Timedoutwaitingfor500msatkotlinx.coroutines.TimeoutKt.TimeoutCancellationException(Timeout.kt:184)atkotlinx.coroutines.TimeoutCoroutine.run(Timeout.kt:154)atkotlinx.coroutines.EventLoopImplBase1.invokeSuspend (File.kt:-1) Caused by: kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 500 ms at kotlinx.coroutines.TimeoutKt .TimeoutCancellationException(Timeout.kt:184) at kotlinx.coroutines.TimeoutCoroutine .run(Timeout.kt:154) at kotlinx.coroutines.EventLoopImplBaseDelayedRunnableTask .run(EventLoop.common.kt:508)

일정 시간이 끝난 후에 종료하고 싶다면 withTimeout을 이용할 수 있다.
취소가 되면 TimeoutCancellationException 예외가 발생한다.

withTimeoutOrNull

import kotlinx.coroutines.*

suspend fun doCount() = coroutineScope {
    val job1 = launch(Dispatchers.Default) {
        var i = 1
        var nextTime = System.currentTimeMillis() + 100L
        
        while(i <= 10 && isActive) {
            val currentTime = System.currentTimeMillis()
            if(currentTime >= nextTime) {
                println(i)
                nextTime = currentTime + 100L
                i++
            }
        }
    }
}

fun main() = runBlocking {
    val result = withTimeoutOrNull(500L) {
        doCount()
        true
    } ?: false
    println(result)
}

1
2
3
4
false

성공할경우 withTimeoutOrNull의 마지막에서 true를 리턴하게 하고 실패한경우 null을 반환하게 되므로 엘비스 연산자를 이용해 false 를 리턴하게 하였다.

0개의 댓글