calling launch within launch is not structured con...
# coroutines
m
calling launch within launch is not structured concurrency anymore right?
Copy code
val upperJob = launch {
 val jobFoo1 = launch {
      foo1()
   }
   val jobFoo2 = launch {
      foo2()  
   } 
}
upperJob.cancel() //won't cancel jobFoo1 and jobFoo2
right? I have to use
async
here
k
what behaviour you want to achieve?
m
I want to load 2 sets of data from 2 differnt APIs, if one fails, the other should not be cancled but I still want to be able to cancel both at once
k
easy, use like this
but, exception from first or second will be propagated up, so you have to handle it
m
how? where to put try catch? and whats the difference here to async?
k
Use try catch for the most upper coroutine or coroutineExceptionHandler. Difference between async/launch you can get in docs
m
okay launch and async will both excute the calls parallel?
k
yep
m
Copy code
launch {
   try{
      supervisorScope {
        // first api
        launch { }
        
        //second api 
        launch { }
    }
   } cathc(e : Exception) {
   } 
}
Copy code
launch {
   try{
      supervisorScope {
        // first api
        async { }
        
        //second api 
        async { }
    }
   } cathc(e : Exception) {
   } 
}
basically both will do what I want?
1
k
but with async you have to call await() to get the result
m
are you android developer?
k
android app developer
m
Copy code
override fun onChanged(t: AccountInfo) {
        fetchDataAfterAccountInfo?.cancel()
        fetchDataAfterAccountInfo = viewModelScope.launch {
            try {
                supervisorScope {
                    launch {
                        val purchases = appService.fetchPurchasesWithPickupData().purchases
                        pickPurchasesLiveData.postValue(purchases)
                    }
                    launch {
                        val paymentPlan = appService.fetchPaymentPlan()
                        paymentPlanLiveData.postValue(paymentPlan.instalments)
                    }
                }
            } catch (e : Exception) {
                Timber.e(e, "loading data after accountInfo failed")
            }
        }
    }
so basically, that's what I want to to do, after a new accountInfo, I load 2 sets of data, which are independent from each other, but I want to be able to cancel both if there is a new accountInfo what do you think?
k
Here is complete code snippet.
m
if first api fails, what's the resultOfAll[0] then?
k
well, yes. move resultOfAll inside supervisor scope
or like this.
m
if first api fails, will second api executes uninfluenced?
k
yes, second will not be cancelled
m
lets maybe go back to my android code snippet, where should I put
liveData.posValue
?
k
let me check
code snippet that you sent is great, so you can use it 😉
m
so if both apis fails for example, I will get two exception, means timber will log 2 times? 🤯
k
yep, no exceptions will be lost
m
okay if it is really like this, then this is for sure too magical
🤯
k
lol
m
@Kirill Prybylsky
need to disappoint
Copy code
fun main() = runBlocking {
    try {
        supervisorScope {
            launch { foo1() }
            launch { foo2() }
            delay(1000)
            println("Waited 1000ms")
        }
    } catch (e : Exception) {
        println(e)
    }
}

suspend fun foo1() {
    delay(300)
    throw RuntimeException("Something went wrong")
}

suspend fun foo2() : Int{
    delay(500)
    println("returning 5")
    return 5
}
Copy code
Exception in thread "main" java.lang.RuntimeException: Something went wrong
	at com.minki.example.MainKt.foo1(Main.kt:20)
	at com.minki.example.MainKt$foo1$1.invokeSuspend(Main.kt)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTaskKt.resume(DispatchedTask.kt:173)
	at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:111)
	at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:305)
	at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl(CancellableContinuationImpl.kt:315)
	at kotlinx.coroutines.CancellableContinuationImpl.resumeUndispatched(CancellableContinuationImpl.kt:397)
	at kotlinx.coroutines.EventLoopImplBase$DelayedResumeTask.run(EventLoop.common.kt:484)
	at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:271)
	at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:79)
	at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:54)
	at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
	at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:36)
	at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
	at com.minki.example.MainKt.main(Main.kt:5)
	at com.minki.example.MainKt.main(Main.kt)
returning 5
Waited 1000ms
uncaught exception is thrown... so launch is a differnce to async
k
expected behaviour of coroutines, put try catch block inside of supervisorScope. i think we missed something.
m
still the same, launch and async have difference exception propagation...
k
s
@mingkangpan Maybe this can help you a bit: “Exceptional Exceptions for Coroutines made easy…?” by Anton Spaans https://link.medium.com/gKOAkJoMw4
👍 1