Android/Coroutine
Android Coroutine - Cancel
강태종
2022. 1. 8. 01:10
cancen() & cancelAndJoin()
Coroutine에서 취소가 어떻게 진행되는지 확인해봅니다.
// cancel과 cancelAndJoin의 차이
public suspend fun Job.cancelAndJoin() {
cancel()
return join()
}
cancel()을 실행했을 때 취소되지 않고 Coroutine이 계속 실행됩니다. 즉 Coroutine 내부에서 진행될 때 취소를 확인하여 종료할 수 있도록 설계 해야합니다. (개발자가 직접 취소 지점을 정할 수 있습니다.)
val job = CoroutineScope(Dispatchers.IO).launch {
for (i in 0 until 5) {
Log.d("PASS", "Running")
Thread.sleep(1000L)
}
}
lifecycleScope.launch {
delay(1000L)
job.cancel()
}
2022-01-08 00:15:14.054 19334-19367/com.taetae98.coroutine D/PASS: Running
2022-01-08 00:15:15.055 19334-19367/com.taetae98.coroutine D/PASS: Running
2022-01-08 00:15:16.057 19334-19367/com.taetae98.coroutine D/PASS: Running
2022-01-08 00:15:17.060 19334-19367/com.taetae98.coroutine D/PASS: Running
2022-01-08 00:15:18.063 19334-19367/com.taetae98.coroutine D/PASS: Running
val job = CoroutineScope(Dispatchers.IO).launch {
for (i in 0 until 5) {
Log.d("PASS", "Running")
Thread.sleep(1000L)
yield()
}
}
lifecycleScope.launch {
delay(1000L)
job.cancel()
}
val job = CoroutineScope(Dispatchers.IO).launch {
for (i in 0 until 5) {
if (isActive) {
Log.d("PASS", "Running")
Thread.sleep(1000L)
}
}
}
lifecycleScope.launch {
delay(1000L)
job.cancel()
}
2022-01-08 00:36:11.718 19690-19721/com.taetae98.coroutine D/PASS: Running
2022-01-08 00:36:12.721 19690-19721/com.taetae98.coroutine D/PASS: Running
위와 같이 2가지 방법으로 코딩했을 경우 정상적으로 종료된 것을 확인할 수 있다. 즉 개발자가 직접 yield(), isActive 등으로 비활성 상태에서 종료 지점을 정할 수 있습니다.
val job = CoroutineScope(Dispatchers.IO).launch {
try {
for (i in 0 until 5) {
Log.d("PASS", "Running")
yield()
delay(1000L)
}
} catch (e: CancellationException) {
Log.d("PASS", "ERROR : $e")
}
}
lifecycleScope.launch {
delay(1000L)
job.cancel()
}
2022-01-08 00:44:19.600 20187-20221/com.taetae98.coroutine D/PASS: Running
2022-01-08 00:44:20.613 20187-20223/com.taetae98.coroutine D/PASS: ERROR : kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job=StandaloneCoroutine{Cancelling}@43635db
실제로 Coroutine이 종료된 시점에서 yield(), delay() 등을 호출하면 CancellationException이 발생하는 것을 확인할 수 있습니다.
NonCancellable
이미 취소된 함수에서 어떠한 작업을 위해 suspend 함수를 호출해야 할 경우 보통의 상황에선 CancellationException이 발생할 것 입니다. withContext(NonCancellable)을 통해서 정지된 Coroutine에서 suspend 함수를 호출할 수 있습니다.
val job = CoroutineScope(Dispatchers.IO).launch {
try {
for (i in 0 until 5) {
Log.d("PASS", "Running")
yield()
delay(1000L)
}
} catch (e: CancellationException) {
Log.d("PASS", "ERROR : $e")
} finally {
withContext(NonCancellable) {
delay(1000L)
Log.d("PASS", "coroutine is finished!!")
}
}
}
lifecycleScope.launch {
delay(1000L)
job.cancel()
}
2022-01-08 00:58:58.531 20284-20319/com.taetae98.coroutine D/PASS: Running
2022-01-08 00:58:59.551 20284-20321/com.taetae98.coroutine D/PASS: ERROR : kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job=StandaloneCoroutine{Cancelling}@43635db
2022-01-08 00:59:00.562 20284-20321/com.taetae98.coroutine D/PASS: coroutine is finished!!
withTimeout
withTimeout을 사용하여 지정된 시간에만 작동되는 Coroutine을 만들 수 있습니다. withTimeout을 통해 Exception이 발생한 경우 TimeoutCancellationException이 발생하고 CancellationException을 상속 받습니다.
public class TimeoutCancellationException internal constructor(
message: String,
@JvmField internal val coroutine: Job?
) : CancellationException(message), CopyableThrowable<TimeoutCancellationException> {
val job = CoroutineScope(Dispatchers.IO).launch {
withTimeout(2000L) {
try {
for (i in 0 until 5) {
Log.d("PASS", "Coroutine is running")
delay(1000L)
}
} catch (e: TimeoutCancellationException) {
Log.e("PASS", "Exception : $e")
}
}
}
2022-01-08 01:03:34.056 20418-20453/com.taetae98.coroutine D/PASS: Coroutine is running
2022-01-08 01:03:35.068 20418-20453/com.taetae98.coroutine D/PASS: Coroutine is running
2022-01-08 01:03:36.058 20418-20453/com.taetae98.coroutine E/PASS: Exception : kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 2000 ms