Kotlin Training Program

DOWNLOAD APP

FEEDBACK

Job

Recall that launch() function is a non-blocking function which launches a coroutine asynchronously and returns a Job object to manage the coroutine. Let us now see how to manage the coroutine using Job object.

join()

We can use the Job#join() suspend function to await the completion of the coroutine.

Observe the following program :

 private val client = HttpClient(CIO)

fun main() {
    runBlocking {
        launch {
            println("response = ${getMathFact(1)}")
        }
        println("Execution complete!")
    }
}

private suspend fun getMathFact(num: Int): String {
    println("sending R#$num...")
    return client.get("http://numbersapi.com/$num/math").bodyAsText()
}

/* Output :

Execution complete!
sending R#1...
response = 1 is the first figurate number of every kind, such as triangular number, pentagonal number and centered hexagonal number, to name just a few.
 */
 

In the output, note that Execution complete! is printed even before sending the request! This is because launch is a non-blocking function that returns immediately and does not wait for the coroutine to finish. To wait for the coroutine to finish, we can use the Job#join() function :

 private val client = HttpClient(CIO)

fun main() {
    runBlocking {
        val job = launch {
            println("response = ${getMathFact(1)}")
        }

        job.join()
        
        println("Execution complete!")
    }
}

private suspend fun getMathFact(num: Int): String {
    println("sending R#$num...")
    return client.get("http://numbersapi.com/$num/math").bodyAsText()
}

/* Output :

sending R#1...
response = 1 is also the first and second numbers in the Fibonacci sequence and is the first number in many other mathematical sequences.
Execution complete!
 */
 

Now the line Execution complete! is printed after completion because join() function awaits coroutine to finish.

joinAll()

join() is used for awaiting single coroutine completion. For awaiting multiple coroutines to finish, you can use the List<Job>#joinAll() function.

Example

Let’s send three network requests in parallel and await their responses.

 val client = HttpClient(CIO)

fun main() {
    runBlocking {
        val jobs = buildList {
            repeat(3) {
                add(
                    launch {
                        println("response = ${getMathFact(1)}")
                    }
                )
            }
        }

        jobs.joinAll()

        println("Execution complete!")
    }
}

suspend fun getMathFact(num: Int): String {
    println("sending R#$num...")
    return client.get("http://numbersapi.com/$num/math").bodyAsText()
}

/* Output :

sending R#0...
sending R#1...
sending R#2...
response = 2 is the third Fibonacci number, and the third and fifth Perrin numbers.
response = 0 is the additive identity.
response = 1 is also the first and second numbers in the Fibonacci sequence and is the first number in many other mathematical sequences.
Execution complete! */
 

Note that the launch() function returns a Job object which is added to the jobs list.

cancel()

To cancel a coroutine after it has been lauched, we can invoke the Job#cancel() function by passing an optional reason.

 import kotlinx.coroutines.*

fun main() {
    runBlocking {
        
        // J#1
        val j1 = launch {
            println("J#1 started...")

            // J#1.1
            launch {
                println("J#1.1 started...")
                delay(200)
                println("J#1.1 ended...")
            }

            delay(500)
            println("J#1 ended...")
        }

        // J#2
        launch {
            println("J#2 started...")
            delay(100)
            j1.cancel("Cancelled by J#2")
            println("J#2 ended...")
        }
    }
}

/* Output :

J#1 started...
J#2 started...
J#1.1 started...
J#2 ended... */
 

Note :

Status

A coroutine can be in one of the following states at any point of time :

To know the current status of a coroutine, we can examine the following properties of its Job object :

Example

 import kotlinx.coroutines.*

fun main() {
    runBlocking {

        // J#1
        val j1 = launch {
            println("J#1 started...")

            // J#1.1
            launch {
                println("J#1.1 started...")
                delay(200)
                println("J#1.1 ended...")
            }

            delay(100)
            println("J#1 ended...")
        }

        // J#2
        launch {
            println("J#2 started...")
            delay(500)

            if (j1.isActive) {
                println("Cancelling J#1...")
                j1.cancel("Cancelled by J#2")
            } else {
                println("J#1 is ${if (j1.isCompleted) "completed" else "cancelled"}!")
            }

            println("J#2 ended...")
        }
    }
}

/* Output :

J#1 started...
J#2 started...
J#1.1 started...
J#1 ended...
J#1.1 ended...
J#1 is completed!
J#2 ended... */
 

Note :

  • Inside J#2 instead of cancelling J#1 directly, its status is examines first. cancel() function is invoked only if it is active otherwise either one of completed or cancelled status is printed.
  • J#1 already completed before J#2’s 500 ms delay, so instead of cancel() being invoked, J#1 is completed! is printed.

Let’s update J#1.1 delay from 200 to 1000 ms in order to make it active when J#2 reads its status.

 import kotlinx.coroutines.*

fun main() {
    runBlocking {

        // J#1
        val j1 = launch {
            println("J#1 started...")

            // J#1.1
            launch {
                println("J#1.1 started...")
                delay(1000)
                println("J#1.1 ended...")
            }

            delay(100)
            println("J#1 ended...")
        }

        // J#2
        launch {
            println("J#2 started...")
            delay(500)

            if (j1.isActive) {
                println("Cancelling J#1...")
                j1.cancel("Cancelled by J#2")
            } else {
                println("J#1 is ${if (j1.isCompleted) "completed" else "cancelled"}!")
            }

            println("J#2 ended...")
        }
    }
}

/* Output :

J#1 started...
J#2 started...
J#1.1 started...
J#1 ended...
Cancelling J#1...
J#2 ended... */