Blog Infos
Author
Published
Topics
Published
Topics

Photo by Bruno Kelzer on Unsplash

 

This is the last article in a series of blog posts about applying Structural concurrency.

 

  • (This post) — Applying Kotlin Structured concurrency: Part IV — Coroutines Cancellation

Structured concurrency helps to cancel coroutines not only individually one by one but also centralised using root coroutine — cancelation of parent coroutine canceling child coroutines too.

CancellationException

Coroutine cancellation is throwing a CancellationException. A cancellation is just a specific type of exception that is treated differently from a failure.

There is a difference in CancellationException propagation in comparison with other exceptions — this exception cancels itself and child coroutines while other exceptions cancel itself, child, siblings and parent coroutines.

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

Jobs

Avoid scope job cancellation

You can cancel parent scope with all child coroutines.

But after cancellation you can’t start coroutine again in that cancelled scope. If you cancelling scope — you cancel all child coroutines.

import kotlinx.coroutines.*

val job = Job()
val scope = CoroutineScope(Dispatchers.Default + job)


fun main() {
    runBlocking {
        scope.launch {
            println("do work 1")
            delay(50)
            println("do more work 1")
        }
        delay(10)
        job.cancel()

        scope.launch {
            delay(10)
            println("do work 2")
        }
    }
}

When we cancel job we put it into the completed state. Coroutines launched in a scope of the completed job will not be executed.

Cancel all coroutines in scope

When you want to cancel all coroutines of a specific scope, you can use cancelChildren() function. Also, it’s a good practice to provide the possibility to cancel individual jobs.

import kotlinx.coroutines.*

val job = Job()
val scope = CoroutineScope(Dispatchers.Default + job)


fun main() {
    runBlocking {
        scope.launch {
            println("do work 1")
            delay(50)
            println("do more work 1")
        }
        delay(10)
        scope.coroutineContext.cancelChildren()

        scope.launch {
            delay(10)
            println("do work 2")
        }
    }
}
Cancel job

You can cancel specific coroutine without affecting siblings cancelling specific job.

import kotlinx.coroutines.*

val job = Job()
val scope = CoroutineScope(Dispatchers.Default + job)


fun main() {
    runBlocking {
        val job1 = scope.launch {
            println("do work 1")
            delay(50)
            println("do more work 1")
        }
        val job2 = scope.launch {
            println("do work 2")
            delay(50)
            println("do more work 2")
        }
        delay(10)
        job1.cancel()
    }
}
Cooperative cancellation

If you try to cancel coroutine during long operation you can not always get expected behaviour:

import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext

private val context: CoroutineContext = Job() + Dispatchers.Default

fun main() {
    CoroutineScope(context).launch {
        val job = launch {
            var i = 0
            while (i < 4) {
                println("$i")
                Thread.sleep(60)
                i++
            }
        }
        delay(100)
        println("Cancel")
        job.cancel()
        println("Done")
    }
    Thread.sleep(500)
}

Coroutines cancellation is cooperative — so you need to check if coroutine was cancelled using job.isActive or ensureActive(). The difference between isActive and ensureActive is that the latter immediately throws a CancellationException if the job is no longer active.

import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext

private val context: CoroutineContext = Job() + Dispatchers.Default

fun main() {
    CoroutineScope(context).launch {
        val job = launch {
            var i = 0
            while (i < 4 && isActive) {
                println("$i")
                Thread.sleep(60)
                i++
            }
        }
        delay(100)
        println("Cancel")
        job.cancel()
        println("Done")
    }
    Thread.sleep(500)
}

But you can fix it even simpler: you can change Thread.sleep() to delay() then it starts to work as expected. Why? All suspend functions from kotlinx.coroutines are cancellable: withContextdelay etc.

import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext

private val context: CoroutineContext = Job() + Dispatchers.Default

fun main() {
    CoroutineScope(context).launch {
        val job = launch {
            var i = 0
            while (i < 4) {
                println("$i")
                delay(60)
                i++
            }
        }
        delay(100)
        println("Cancel")
        job.cancel()
        println("Done")
    }
    Thread.sleep(500)
}

There is one more useful function — yeild(). In addition to checking the cancellation status of the job, the underlying thread is released and is made available for other coroutines.

Don’t catch cancellation exception

You should remember — coroutines cancellation works by trowing CancellationException so you should have separate catch for it.

You should strive to make your suspending functions cancellable. A suspending function can be made of several suspending functions. All of them should be cancellable.

try {
       someWork()
   } catch (e: Throwable) {
       if (e is CancellationException) {
           throw e
       }
       ...
   }

Thank you for reading!

This article was previously published on proandroiddev.com

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
Hi, today I come to you with a quick tip on how to update…
READ MORE
blog
Automation is a key point of Software Testing once it make possible to reproduce…
READ MORE
blog
Drag and Drop reordering in Recyclerview can be achieved with ItemTouchHelper (checkout implementation reference).…
READ MORE

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

Menu