Starting a coroutine with async

When a coroutine is started with the intention of processing its result, async() must be used. It will return a Deferred<T>, where Deferred is a non-blocking cancellable future – provided by the coroutines' framework – and T is the type of the result.

When using async, you must not forget to process its result – which can easily happen, so be wary.

Consider the following code:

fun main(args: Array<String>) = runBlocking {
val task = async {
doSomething()
}
task.join()
println("Completed")
}

Here, doSomething() is simply throwing an exception:

fun doSomething() {
throw UnsupportedOperationException("Can't do")
}

You may be tempted to assume that this will stop the execution of the application, the stack of the exception will be printed, and the app's exit code will be different from zero – zero means that no error happened, anything different means error. Let's see what happens when this is executed:

As you can see, there is no exception stack trace being printed in logs, the application didn't crash, and the exit code indicates successful execution.

This is because any exception happening inside an async() block will be attached to its result, and only by checking there you will find the exception. For this, the isCancelled and getCancellationException() methods can be used together to safely retrieve the exception. The previous example can be updated so that it validates the exception, like shown here:

fun main(args: Array<String>) = runBlocking {
val task = async {
doSomething()
}
task.join()
if (task.isCancelled) {
val exception = task.getCancellationException()
println("Error with message: ${exception.message}")
} else {
println("Success")
}
}
At the time of writing, there is an effort from the Kotlin team to make the code above work as explained here. If you notice that isCancelled is returning false in the scenario above, please replace it with isCompletedExceptionally. I decided to avoid mentioning isCompletedExceptionally because it's being deprecated close to the release of the book. For more information, please read issue 220 in the coroutines' GitHub repository.

This will then print the following error:

In order to propagate the exception,  await() can be called on Deferred, for example:

fun main(args: Array<String>) = runBlocking {
val task = async {
doSomething()
}
task.await()
println("Completed")
}

This will crash the application:

This crash happens because by calling await(), we are unwrapping Deferred, which in this case will unwrap to the exception and propagate it.

The main difference between waiting with join() and then validating and processing any error, and calling await() directly, is that in the first option we are handling the exception without having to propagate it, whereas by just calling await() the exception will be propagated.

For this reason, the example using await() will return the code one, meaning an error during the execution, whereas waiting with join() and using isCancelled and getCancellationException() to handle the error will result in a successful code of zero.

In Chapter 3, Life Cycle and Error Handling, we will talk more about proper exception handling and propagation depending on the expected behavior.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.21.98.207