| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 8 | 9 | 10 | 11 | 12 | 13 | 14 |
| 15 | 16 | 17 | 18 | 19 | 20 | 21 |
| 22 | 23 | 24 | 25 | 26 | 27 | 28 |
- 팩토리 메소드
- 코틀린멀티플랫폼
- 디자인패턴 #
- Design Pattern
- ㅋㅁ
- Functional Programming
- builderPattern
- Abstract Factory
- Coroutines
- compose
- PrototypePattern
- 코틀린
- 디자인패턴
- factory method
- 추상팩토리패턴
- material3
- 코루틴
- 안드로이드 디자인시스템
- Observer Pattern
- 옵저버 패턴
- 함수형프로그래밍
- define
- 프로토타입 패턴
- 추상 팩토리
- kotlin multiplatform
- kmp
- Kotlin
- 빌터패턴
- android designsystem
- designPattern
- Today
- Total
오늘도 더 나은 코드를 작성하였습니까?
9장 취소 본문
취소는 매우 중요하여 중단함수를 사용하는 몇몇 클래스와 라이브러리는 취소를 반드시 지원한다.
기본적인 취소
job 인터페이스는 취소를 가능하게 하는 cancel 메서드를 가지고 있다.
cancel
- 첫 번째 중단점에서 job을 종료한다.
- 자식 코루틴을 가지고 있다면 종료한다. 하지만 부모에게는 영향을 주이 않는다.
- 취소되면, 새로운 코루틴의 부모가 될수 없다.
import kotlinx.coroutines.*
suspend fun main(): Unit = coroutineScope {
val job = launch {
repeat(1_000) { i ->
delay(200)
println("printing $i")
}
}
delay(1100)
job.cancel()
job.join()
println("Cancelled successfully")
/*
printing 0
printing 1
printing 2
printing 3
printing 4
Cancelled successfully
*/
cancel 함수에 예외를 인자로 넣어 취소 이유를 명확히 할수 있습니다.
canlcel이 호출된 이후 취소 과정이 완료되는걸 기다리기 위해 join을 사용하는것이 일반적이다.
import kotlinx.coroutines.*
suspend fun main(): Unit = coroutineScope {
val job = launch {
repeat(1_000) { i ->
delay(100)
Thread.sleep(100)
println("printing $i")
}
}
delay(1000)
job.cancel()
// job.join()
println("Cancelled successfully")
}
/*
printing 0
printing 1
printing 2
printing 3
Cancelled successfully
printing 4
*/
join의 호출이 없다면, race condition이 발생하여, Cancelled successfully뒤에 printing 4가 출력된다.
job.cancel()은 코루틴에게 "이제 그만해"라고 신호를 보내는 것입니다. 하지만 코루틴이 그 신호를 받고 실제로 종료(Cleanup)되는 데는 시간이 걸립니다.
- 코드에는 **Thread.sleep(100)**이 포함되어 있습니다.
- 만약 cancel()이 호출된 시점에 코루틴이 Thread.sleep을 실행 중이라면, 그 100ms가 다 지나고 다음 delay를 만날 때까지 코루틴은 죽지 않고 살아있습니다.
* join 자식 코루틴의 작업이 완료를 기다리는 함수.
import kotlinx.coroutines.*
suspend fun main(): Unit = coroutineScope {
val job = launch {
repeat(1_000) { i ->
delay(100)
Thread.sleep(100)
println("printing $i")
}
}
delay(1000)
job.cancel()
job.join()
println("Cancelled successfully")
}
/*
printing 0
printing 1
printing 2
printing 3
printing 4
Cancelled successfully
*/
cancel() join()을 합친 cancelAndJoin() 확장함수를 제공한다
Job팩토리 함수로 생성된 job도 같은 방법을 통해 취소 가능하며, job에 딸리 수많은 코루틴을 한번에 취소할때 자주 사용된다.
import kotlinx.coroutines.*
suspend fun main(): Unit = coroutineScope {
val job = Job()
launch(job) {
repeat(1_000) { i ->
delay(200)
println("printing $i")
}
}
delay(1100)
job.cancelAndJoin()
println("Cancelled successfully")
}
/*
printing 0
printing 1
printing 2
printing 3
printing 4
Cancelled successfully
*/
취소는 어떻게 작동하는가?
job이 취소되면 Cancelling 상태가 된다.
첫 번째 중단점에서 CancellationException을 던진다.
try-catch로 잡을수 있지만, 다 던지는것이 좋다.
import kotlinx.coroutines.*
suspend fun main(): Unit = coroutineScope {
val job = Job()
launch(job) {
try {
repeat(1_000) { i ->
delay(200)
println("Printing $i")
}
} catch (e: CancellationException) {
println(e)
throw e
}
}
delay(1100)
job.cancelAndJoin()
println("Cancelled successfully")
delay(1000)
}
//Printing 0
//Printing 1
//Printing 2
//Printing 3
//Printing 4
//kotlinx.coroutines.JobCancellationException: Job was cancelled; job=JobImpl{Cancelling}@692f3e3
//Cancelled successfully
코루틴의 취소는 내부적으로 CancellationException이라는 예외를 던지는 방식을 사용한다.
finally 블록에서 자원의 정리가 가능합니다.
suspend fun main(): Unit = coroutineScope {
val job = Job()
launch(job) {
try {
delay(Random.nextLong(2_000))
println("Done!")
} finally {
println("Will always be printed")
}
}
delay(1000)
job.cancelAndJoin()
}
취소 중 코루틴을 한 번 더 호출하기.
CancellationException 예외를 잡고, 후처리 과정에서 자원을 정리할 필요가 있다면, 계속해서 실행할 수 있다.
하지만, 중단되거나 다른 코루틴을 시작하는 것은 허용되지 않는다.
import kotlinx.coroutines.*
suspend fun main(): Unit = coroutineScope {
val job = Job()
launch(job) {
try {
delay(2_000)
println("Job is Done!")
} finally {
println("Finally")
launch { // 새로운 코루틴 무시.
println("Will not be printed")
}
delay(1_000) // 예외 발생지점.
println("Will always be printed")
}
}
delay(1000)
job.cancelAndJoin()
println("Cancel done")
}
//Finally
//Cancel done
코루틴이 이미 취소 되었음에도 중단 한수를 반드시 호출해야 하는 경우.
withContext(NonCancellable)으로 포장하는 방법을 많이 사용한다.
취소할 수 없는 Job인 NonCancellable을 사용한다.
import kotlinx.coroutines.*
suspend fun main(): Unit = coroutineScope {
val job = Job()
launch(job) {
try {
delay(2_000)
println("Job is Done!")
} finally {
println("Finally")
withContext(NonCancellable) {
delay(1_000)
println("CleanUp done")
}
}
}
delay(100)
job.cancelAndJoin()
println("Done")
}
//Finally
//CleanUp done
//Done
invokeOnCompletion
invokeOnCompletion
- Completed, Cancelled 등 마지막 상태에 도달했을 때, 호출될 핸들러를 지정
import kotlinx.coroutines.*
suspend fun main(): Unit = coroutineScope {
val job = launch {
delay(1000L)
}
job.invokeOnCompletion { exception: Throwable? ->
println("Finished")
}
delay(400L)
job.cancelAndJoin()
}
//Finished
- job이 예외 없이 끝나면 null
- 코루틴이 취소되었으면 CancellationException이
- 코루틴을 종료시킨 예외일 수 있습니다.
job이 invokeOnCompletion이 호출되기 전에 완료되었으면, 핸들러는 즉시 호출됩니다.
import kotlinx.coroutines.*
import kotlin.random.Random
suspend fun main(): Unit = coroutineScope {
val job = launch {
delay(Random.nextLong(2400))
println("Finished")
}
delay(800)
job.invokeOnCompletion { exception: Throwable? ->
println("Will always be printed")
println("The exception was: $exception")
}
delay(800)
job.cancelAndJoin()
}
//Will always be printed
//The exception was: kotlinx.coroutines.JobCancellationException
//또는
//Finished
//Will always be printed
//The exception was: null
중단될 수 없는 걸 중단하기
취소는 중단점에서 발생하기 때문에 중단점이 없다면, 취소할 수 없습니다.
Thread.sleep을 사용한 구현은 정말 나쁜 방식이기에 현업에선 절대로 사용하면 안됩니다.
import kotlinx.coroutines.*
suspend fun main(): Unit = coroutineScope {
val job = Job()
launch(job) {
repeat(1000) { i ->
Thread.sleep(200)
//파일을 읽는 등의 작업이 있다고 가정합니다.
println("Printing $i")
}
}
delay(1000)
job.cancelAndJoin()
println("Canceled successfully")
delay(1000)
}
//Printing 0
//Printing 1
//Printing 2
//Printing 3
//Printing 4
//Printing 5
//1000까지 찍힘
이에 대한 해결방법으로는
1. yield(코루틴을 중단후 즉시, 재실행) 주기적으로 호출해준다.
중단 가능하지 않으면서 cpu 집약적이거나 시간이 오래걸리는 연산이 중단함수에 있다면, 각 연산들 사이에 yield를 사용하는것이 좋습니다.
import kotlinx.coroutines.*
suspend fun main(): Unit = coroutineScope {
val job = Job()
launch(job) {
repeat(1000) { i ->
Thread.sleep(200)
//파일을 읽는 등의 작업이 있다고 가정합니다.
yield()
println("Printing $i")
}
}
delay(1100)
job.cancelAndJoin()
println("Canceled successfully")
delay(1000)
}
2. job의 상태를 추적하는 것.
isActive를 이용하여 잡이 액티브한지 확인할 수 있고, 그렇지 않다면 연산을 중단할수 있다.
* cancelAndJoin() 호출후 즉시 멈추지 않음.
import kotlinx.coroutines.*
suspend fun main(): Unit = coroutineScope {
val job = Job()
launch(job) {
do {
Thread.sleep(200)
println("Printing")
} while (isActive)
}
delay(1100)
job.cancelAndJoin()
println("Canceled successfully")
}
3. ensureActive() Job이 엑티브 상태가 아니면, CancellationException을 던지는 함수.
import kotlinx.coroutines.*
suspend fun main(): Unit = coroutineScope {
val job = Job()
launch(job) {
repeat(1000) { num ->
Thread.sleep(200)
ensureActive()
println("Printing $num")
}
}
delay(1100)
job.cancelAndJoin()
println("Canceled successfully")
}
* ensureActive()가 좀 더 가볍워 더 선호 되고있다. yield() 전형적인 최상위 중단 함수이다. 스코프가 필요하지 않기 때문에 일반 중단함수에서도 사용가능하다.
suspendCancellableCoroutine
결과가 나올 때까지 이 코루틴을 잠깐 멈춰줘, 그런데 만약 취소되면, 중단된 작업도 같이 취소해줘!"**라고 명령하는 도구입니다.
public suspend inline fun <T> suspendCancellableCoroutine(
crossinline block: (CancellableContinuation<T>) -> Unit
): T
가장 중요한 메서드는 코루틴이 취소되었을때 행동을 정의하는 데 사용되는 invokeOnCancellation으로 라이브러리의 실행을 취소하거나 자원을 해제할때 주로 사용한다.
이미 정의된 콜백 방식의 API(예: 네트워크 라이브러리, 위치 정보 등)를 코루틴으로 래핑할 때 사용
취소는 적절하게 사용하면 자원낭비와 메모리 누수를 줄일 수 있다. 애플리케이션의 성능을 개선할 수 있습니다.
'Coroutine > 코틀린 코루틴' 카테고리의 다른 글
| 10장 예외처리 (0) | 2026.02.09 |
|---|---|
| 8장 Job과 자식 코루틴 기다리기. (0) | 2026.01.26 |
| 7장 코루틴 컨텍스트 (1) | 2026.01.19 |
| 6장 코루틴 빌더 (0) | 2026.01.05 |
| 5장 코루틴 언어차원에서의 지원 vs 라이브러리 (0) | 2025.12.22 |