본문으로 바로가기

취소와 타임아웃 #3

category Book & Lecture/FastCampus 2022. 4. 15. 21:37

취소와 타임아웃 #3

목차

    Job에 대해 취소

    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!")
    }

    job1은 1000ms 동안 지연 되기 때문에 3!은 출력이 안됨.

    취소 불가능한 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()
    }

    doCount Done!이 작업이 끝나기 전에 출력이 됨.

    실제 job1이 취소가 되지 않음.

    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()
    }

    cancel 이후에 join을 넣으면 doCount가 끝날 때 doCount Done!가 출력 됨.

    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()
    }

    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()
    }

    finally를 같이 사용

    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!")
    }

    취소 불가능한 블록

    어떤 코드는 취소가 불가능해야 함.

    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!")
    }

    타임 아웃

    일정 시간이 끝난 후 종료하고 싶으면 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()
        }
    }

    취소가 되면 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)
    }

    성공할 경우 withTimeoutOrNull의 마지막에서 true를 리턴하고, 실패했을 경우 null을 반환

    'Book & Lecture > FastCampus' 카테고리의 다른 글

    스코프 빌더, 잡 #2  (0) 2022.04.15
    스코프 빌더 #1  (0) 2022.04.14
    코루틴 등장 #0  (0) 2022.04.14
    대용량 서비스를 위한 아키텍처 with Redis #0  (0) 2021.11.29