Job에 대해 취소
명시적인 Job에 대해 cancel 메서드를 호출해 취소할 수 있습니다.
( * Join 은 어떤 행동이 끈날때까지 대기하는 것 )
import kotlinx.coroutines.*
suspend fun doOneTwoThree() = coroutineScope {
val job1 = launch {
println("launch1: ${Thread.currentThread().name}")
delay(1000L)
println("3!")
}
val job2 = launch {
println("launch2: ${Thread.currentThread().name}")
println("1!")
}
val job3 = launch {
println("launch3: ${Thread.currentThread().name}")
delay(500L)
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
launch2: main @coroutine#3
1!
launch3: main @coroutine#4
2!
4!
runBlocking: main @coroutine#1
5!
* cancel() 을 통해서 취소를 할 수 있는데, 그전에 Job을 받아와야 한다.
취소 불가능한 Job
launch(Dispatchers.Default)는 그 다음 코드 블록을 다른 스레드에서 수행을 시킬 것입니다.
나중에 자세히 알아볼테니 지금은 넘어갑시다.
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
doCount Done!
2
3
4
5
6
7
8
9
10
* 취소를 못한이유 - job을 통해서 cancel 할수 있게 이해?를 못시켰다.
두가지 부분이 신경이 쓰입니다.
- job1이 취소든 종료든 다 끝난 이후에 doCount Done!을 출력하고 싶다.
- 취소가 되지 않았다.
먼저 취소든 종료든 다 끝난 이후에 doCount Done!을 출력합시다.
=> 위의 문제를 해결하기 위해서는 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을 넣어서 실제로 doCount가 끝날 때 doCount Done!가 출력하게 했습니다.
= > 캔슬을 시도후, 모두 종료 될 때까지 기다리기 위해 join을 사용 .
* 하지만 캔슬은 시키지 못했다.
cancelAndJoin
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()
}
결과는 cancel을 하고 join을 하는 것과 동일.
그럼 실제로 취소할라면 우째야해 ??
cancel 가능한 코루틴 isActive
isActive를 호출하면 해당 코루틴이 여전히 활성화된지 확인할 수 있습니다.
isActive를 루프에 추가해봅시다.
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를 같이 사용( 작업취소후 자원 해제 )
-사용 예시 ( file , socket
launch에서 자원을 할당한 경우에는 어떻게 정리해야할까요?
suspend 함수들은 JobCancellationException를 발생하기 때문에
표준 try catch 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!")
}
catch에 예외발생시 처리 하도록 한다.
위의 예제에서는 finally에서 했다.
취소 불가능한 블록
어떤 코드는 취소가 불가능해야 합니다.
withContext(NonCancellable)을 이용하면 취소 불가능한 블록을 만들 수 있습니다.
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("launch1: ${Thread.currentThread().name}")
delay(1000L)
println("1!")
}
delay(1000L)
print("job2: end")
}
val job3 = launch {
withContext(NonCancellable) {
println("launch1: ${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
launch1: main @coroutine#3
launch1: main @coroutine#4
4!
3!
1!
2!
runBlocking: main @coroutine#1
5!
취소 불가능한 코드를 finally절에 사용할 수도 있습니다.
finally중에도 취소될수 있기때문에 withContext(NonCancellable)을 이용.
타임 아웃
일정 시간이 끝난 후에 종료하고 싶다면 withTimeout을 이용할 수 있습니다.
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()
}
}
실행결과
doCount()
}
}
1
2
3
4
Exception in thread "main" kotlinx.coroutines.
TimeoutCancellationException: Timed out waiting for 500 m
취소가 되면 TimeoutCancellationException 예외가 발생합니다.
withTimeoutOrNull
예외를 핸들하는 것은 귀찮은 일입니다.
withTimeoutOrNull을 이용해 타임 아웃할 때 null을 반환하게 할 수 있습니다.
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
성공할 경우 whithTimeoutOrNull의 마지막에서
true를 리턴하게 하고 실패했을 경우 null을 반환할테니
엘비스 연산자(?:)를 이용해 false를 리턴하게 했습니다.
엘비스 연산자는 null 값인 경우에 다른 값으로 치환합니다.
코틀린의 예외는 식(expression)이어 활용이 어렵지는 않습니다만
개인적으로는 null을 리턴하고 엘비스 연산자로 다루는게 더 편한 것 같습니다.
'Android 공부 > Coroutine' 카테고리의 다른 글
[XX캠퍼스] 05. Kotlin Coroutines & Flow ( 컨텍스트와 디스패처 ) (0) | 2022.07.25 |
---|---|
[XX캠퍼스] 04. Kotlin Coroutines & Flow ( 서스팬딩 함수 ) (0) | 2022.07.22 |
[XX캠퍼스] 02. Kotlin Coroutines & Flow ( 잡,구조화된동시성 ) (0) | 2022.07.20 |
[XX캠퍼스] 01. Kotlin Coroutines & Flow ( 스코프빌더 ) (0) | 2022.07.19 |
코루틴 Flow 참고 블로그 주소 (0) | 2022.07.16 |