Kotlin Coroutines -Cancellation And Timeout
One of Kotlin Coroutines features is that it has Built-in cancellation support. In real life we may need more control over our coroutines. we might want to cancel an API call when the user leaves for another page.
Each Coroutine Scope or Job have Cancel() extension function that cancels this caller, including all it’s children.
In the next example we will be using Lunch to trigger our Api to get some cat facts and then we will see how we can have more control over it.
Let’s say I wanted to cancel this catJob when the user clicks some button. And let the cancel body be :
since we cant test this fast enough lets alter our api function to get 1000 cat facts and meanwhile I’ll trigger cancel. here is what I got:
As you can see once cancel is hit catJob got cancelled and stopped calling the api.
Exceptions Exceptions!
The reason that we got “cats got some exception” line , or in other words reached the catch block in this case is that all the suspending functions in kotlinx.coroutines
are cancellable. They check for cancellation of coroutine and throw CancellationException when cancelled.
Cancellation is Cooperative
let’s change our function a little bit and make it just print cat count and try to cancel it.
and the outcome is the following :
Notice how cat count kept increasing even after cancellation ! that because if a coroutine is working in a computation and does not check for cancellation, then it cannot be cancelled.
This can be checked in the following ways:
1- Check for isActive flag in each iteration or before starting any heavy computation
2- Using yield function
3- using ensureActive function
But Wait! Something is fishy here
why did the original call to the api got canceled without any of these two approaches ?
that is because each suspend function in kotlinx.coroutines
launch, delay, etc.. is a suspension point that checks and throws cancellation exception when reached and causing the coroutine to be cancelled . Now Knowing that my getCats() function is using launch internally then it is cooperative for cancellation .
Don’t believe me? try replacing Thread.sleep with delay and see for yourself !
Closing resources
What shall we do to our open resources in case of cancellation? Closing resources and finalizing any work left can be done in a regular manner, for example try{} finally{} blocks :
The resulted log:
TimeOut
Coroutines have made handling timeout easier on us, so instead of having to track it manually we can use withTimeOut that throws TimeOutCancellationException which is a subclass of CancellationException.
And the resulted log:
Android Lifecycle Aware Scopes And Cancellation
Each of Android’s lifecycle aware scopes trigger cancelation by default on the following conditions:
ViewModelScope : Any coroutine launched in this scope is automatically canceled if the ViewModel
is cleared.
LifecycleScope: Any coroutine launched in this scope is canceled when the Lifecycle
is destroyed.
Whats Next?
Cancellation is one part of the story, but what about error handling?