Why async throwing exception without calling await() method?

As per documentation coroutine created by using async builder will throw exception (if its there) after calling await() method on the coroutine. Then why the following code throws exception although await() method has not been called.

  override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        runBlocking {
            async {
                throw ArithmeticException() // This should not throw an exception until await() is not called
            }
        }
    }

Note: If GlobalScope or any other scope is used with async builder then it does not throw exception but without scope (directly in the runBlocking) it throws exception.

Thank you.

1 Like

Lazily started async:

Optionally, async can be made lazy by setting its start parameter to CoroutineStart.LAZY. In this mode it only starts the coroutine when its result is required by await, or if its Job’s start function is invoked.

runBlocking {
    async(start = CoroutineStart.LAZY) {
        throw ArithmeticException() // This should not throw an exception until await() is not called
    }
}
1 Like

Docs say that it fails the parent scope. You can avoid this by using a SupervisorJob.

The resulting coroutine has a key difference compared with similar primitives in other languages and frameworks: it cancels the parent job (or outer scope) on failure to enforce structured concurrency paradigm.

I don’t know to which place in docs your refer to, but it probably doesn’t say async() throws only after using await(). It says an error in async() is thrown by await(), but as @nickallendev said, it also fails its parent coroutine. If you use GlobalScope your coroutine is detached, so it doesn’t fail anything. Which is actually a disadvantage, because you may very easily miss errors.

1 Like

Maybe I’m getting this wrong, but I think the OP was confused about the fact that async seemingly is meant to be lazy (i.e. it shouldn’t start executing or do anything really until it is awaited). I can see how that behaviour can be surprising, but that expected behaviour is what CoroutineStart.LAZY provides

Thank you for your reply. But if we made launch lazy by setting start parameter to CoroutineStart.LAZY it will also not throw the exception. Then what’s the difference ?

 val job: Job = launch (start = CoroutineStart.LAZY) {
   throw ArithmeticException() // This will not throw the exception until calling job.start() method
        }

Thank you for your reply. Yes, you are right that if a child coroutine fails (throws exception) then the parent will also fail and hence all other children. but here the question is about when the coroutine/builder should throw the exception ? launch builder throws exception when it is occurred and async throws when await() method is called but in the asked question async is throwing exception even without calling await() method.

Honestly, I’m a little confused :slight_smile: The way how it works is that whenever an exception is thrown in a coroutine, it is immediately propagated to parent coroutines. Without waiting for anything.

If you use CoroutineStart.LAZY then the code inside async() is not started automatically, it waits for await() or something similar. So it doesn’t throw, because it doesn’t even run to the point where exception happen.

1 Like

Same here. I think the whole machinery of coroutines is confusing.

The following code throws an exception

 runBlocking {
            async {
                throw ArithmeticException()
            }
        }

Now, if we add a scope to async it will not throw an exception

 runBlocking {
            lifecycleScope.async {
                throw ArithmeticException()
            }
        }

By “confused” I meant that I’m not sure what is unclear to you :wink:

It throws exception in both cases. But in the second case you changed the scope of the coroutine, so it is entirely detached from the runBlocking(). It throws exception inside lifecycleScope, not in runBlocking().

This is not really specific to coroutines, but to processing asynchronous tasks in general. Usually, tasks are structured as a tree where each task can consists of multiple subtasks. In coroutines, failed subtask automatically fails the parent task. So the question is how you strucuture these tasks. In your first example the coroutine started in async() is a subtask of runBlocking(). In the second example, it is a subtask of lifecycleScope. runBlocking() doesn’t wait for it, it doesn’t care about it. As a matter of fact, runBlocking() in this example really does nothing and could/should be removed.

1 Like

This post was flagged by the community and is temporarily hidden.