Home » Kotlin Coroutines launch vs async

Kotlin Coroutines launch vs async

Kotlin Coroutines launch vs async

1. Overview

In this article, we will learn the Kotlin Coroutines launch vs async builder methods. To learn more about Kotlin, refer to these articles.

2. Kotlin Coroutines

A coroutine is a concurrency design pattern you can use to simplify code that executes asynchronously.

It takes a block of code to run, that works concurrently with the rest of the code. It is not bound to any thread. It may suspend its execution in one thread and resume in another one.

Kotlin coroutines are extremely inexpensive when compared with threads. Each time when we want to start a new task asynchronously, we can create a new coroutine. To start a new coroutine, we use one of the main “coroutine builders”: launch, async, or runBlocking.

JetBrains developed the rich kotlinx.coroutines library as part of version 1.3 for coroutines.

3. Kotlin launch vs async coroutines

The launch launches a new coroutine concurrently with the rest of the code, which continues to work independently.

The async creates a coroutine and can return the result of the asynchronous task.

launchasync
Fire and forgetStart a coroutine that returns some result.
This returns a reference to the coroutine as a Job.This returns a reference to the coroutine as a Deferred<T> whereas T refers to the type of the result.
Can’t get results from the
asynchronous task
Can retrieve results from the asynchronous task. The Deferred<T> returns the result of type T.
You can block and wait for the task to complete using the join method and does not propagate the
exception
You can use await or awaitAll to wait and retrieve the result.
Kotlin coroutine launch vs async methods

4. Kotlin launch coroutines

The launch coroutine builder launches a new coroutine without blocking the current thread and returns a reference to the coroutine as a Job.

fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext, 
    start: CoroutineStart = CoroutineStart.DEFAULT, 
    block: suspend CoroutineScope.() -> Unit
): Job

Here is an example of the launch:

fun main()  = runBlocking() {
    println("main() : ${Thread.currentThread().name}")
    val job: Job = launch { 
                println("Launch : ${Thread.currentThread().name}")
                println("Launch :  ${Thread.currentThread().name}}")
    }
   println("main() : ${Thread.currentThread().name} ")
}

If you execute the above code, the following logs appear on the console. As you can see, the code inside the launch block executes asynchronously.

main(): main @coroutine#1 
main(): main @coroutine#1
Launch: main @coroutine#2 
Launch:  main @coroutine#2

4.1. Cancel launch coroutines job

You can call cancel on the resulting job to cancel the coroutine:

fun main()  = runBlocking() {
    println("main(): ${Thread.currentThread().name}")
    val job: Job = launch { 
                println("Launch: ${Thread.currentThread().name}")
                println("Launch:  ${Thread.currentThread().name}")
    }
   job.cancel()
   println("main(): ${Thread.currentThread().name}")
}
/* prints
main(): main @coroutine#1 
main(): main @coroutine#1 
*/

4.1. Customize launch coroutines start

By default, the coroutine is immediately scheduled for execution.

However, you can customize the start option by using the start parameter.

For example, the following launch coroutine builder contains the start parameter as CoroutineStart.LAZY to start coroutine lazily. Here, the returned coroutine Job is in the new state initially and later we explicitly started the coroutine by invoking the start function of the Job.

fun main()  = runBlocking() {
    println("main(): ${Thread.currentThread().name}")
    val job: Job = launch(Dispatchers.Default, CoroutineStart.LAZY) { 
                println("Launch: ${Thread.currentThread().name}")
                println("Launch corotine:  ${Thread.currentThread().name}")
    }
    println("Delaying launch code execution to start")
    delay(1000)
    job.start()
    println("main(): ${Thread.currentThread().name}")
}
/* prints 
main(): main @coroutine#1
Delaying launch code execution to start
main(): main @coroutine#1 
Launch: DefaultDispatcher-worker-1 @coroutine#2
Launch:  DefaultDispatcher-worker-1 @coroutine#2
*/

4.3. launch Coroutines exception

Any uncaught exceptions in this coroutine would also cancel the parent job in the context by default. It means when launch is applied within another coroutine, then any uncaught exception leads to the cancellation of the parent coroutine as well.

For example, the following main method contains launch coroutine within another parent launch coroutine. If the child coroutine throws any uncaught exception, both parent and child coroutine would be canceled.

fun main()  {
    runBlocking {
        launch {
    	    println("parent: ${Thread.currentThread().name}")
            val job: Job = launch { 
                println("Launch: ${Thread.currentThread().name}")
                val i = 100/0     // throw exception 
                println("Launch:  ${Thread.currentThread().name}")
            }
            job.join()
    	    println("parent: ${Thread.currentThread().name}")
        }
   }
}

If you execute the above code, then you can see the following log statements.

parent: main @coroutine#2
Launch: main @coroutine#3
Exception in thread "main" java.lang.ArithmeticException: / by zero
 at FileKt$main$1$1$job$1.invokeSuspend (File.kt:14) 
 at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith (ContinuationImpl.kt:33) 
 at kotlinx.coroutines.DispatchedTask.run (DispatchedTask.kt:106) 

However, you can control this behavior by specifying the CoroutineExceptionHandler.

5. Kotlin Coroutines async builder

Creates a coroutine and returns its future result as an implementation of Deferred. Deferred<T> can return results of type T from the asynchronous task.

fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext, 
    start: CoroutineStart = CoroutineStart.DEFAULT, 
    block: suspend CoroutineScope.() -> T
): Deferred<T>

For example, the following async method returns a string as output. You can wait for the result using the await or awaitAll method.

fun main()  = runBlocking() {
    println("main(): ${Thread.currentThread().name}")
    val deferred: Deferred<String> = async { 
                println("async: ${Thread.currentThread().name}")
                println("async:  ${Thread.currentThread().name}")
                "Hello World"
    }
   val result = deferred.await()
   println(result)
   println("main(): ${Thread.currentThread().name}")
}
/* prints
main(): main @coroutine#1
async corotine: DefaultDispatcher-worker-1 @coroutine#2
async corotine:  DefaultDispatcher-worker-1 @coroutine#2
Hello World
main(): main @coroutine#1 
*/

5.1. Cancel Kotlin async coroutine

You can cancel the running coroutine by calling the cancel method on the deferred object.

fun main()  = runBlocking() {
    println("main(): ${Thread.currentThread().name}")
    val deferred: Deferred<String> = async { 
                println("async: ${Thread.currentThread().name}")
                println("async:  ${Thread.currentThread().name}")
                "Hello World"
    }
   deferred.cancel()
    println("main(): ${Thread.currentThread().name}")
}
/* prints
main(): main @coroutine#1
main(): main @coroutine#1 
*/

5.2. Start async coroutine lazily

By default, the coroutine is immediately scheduled for execution.

However, you can customize the start option by using the start parameter.

For example, the following launch coroutine builder contains the start parameter as CoroutineStart.LAZY to start coroutine lazily. Here, the returned coroutine Deferred is in the new state initially and later we explicitly started the coroutine by invoking the start function of the Deferred.

fun main()  = runBlocking() {
    println("main() method: ${Thread.currentThread().name}")
    val deferred: Deferred<String> = async(Dispatchers.Default, CoroutineStart.LAZY) { 
                println("async corotine: ${Thread.currentThread().name}")
                println("async corotine:  ${Thread.currentThread().name} ")
                "Hello World"
    }
   deferred.start()
   val result = deferred.await()
   println(result)
   println("main() method on ${Thread.currentThread().name}")
}
/* prints
main() method: main @coroutine#1
async corotine: DefaultDispatcher-worker-1 @coroutine#2
async corotine:  DefaultDispatcher-worker-1 @coroutine#2
Hello World
main() method on main @coroutine#1
*/

5.3. Coroutine async exception

It cancels the parent job (or outer scope) on failure to enforce a structured concurrency paradigm.

For example, the following child async throws an uncaught exception and results in the cancellation of parent.

fun main()  {
    runBlocking {
        async {
    		println("parent launch: ${Thread.currentThread().name}")
            val deferred: Deferred<Unit> = async { 
                println("Launch corotine: ${Thread.currentThread().name}")
                val i = 100/0    // throw exception  
                println("Launch corotine:  ${Thread.currentThread().name}")
            }
            deferred.join()
    		println("parent launch: ${Thread.currentThread().name} ")
        }
    }
}
/* prints
parent launch: main @coroutine#2 
Launch corotine: main @coroutine#3 
Exception in thread "main" java.lang.ArithmeticException: / by zero
 at FileKt$main$1$1$deferred$1.invokeSuspend (File.kt:14) 
 at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith (ContinuationImpl.kt:33) 
 at kotlinx.coroutines.DispatchedTask.run (DispatchedTask.kt:106) 
*/

6. Conclusion

To sum up, we have learned the Kotlin Coroutines launch vs async builders with examples.

2 thoughts on “Kotlin Coroutines launch vs async”

  1. Pingback: Kotlin supervisorScope vs coroutineScope - TedBlob

  2. Pingback: Kotlin coroutines nested launch - TedBlob

Leave a Reply

Your email address will not be published.