https://kotlinlang.org logo
Title
c

Colton Idle

05/20/2023, 2:04 PM
Sanity check time! If I have this suspend function
suspend fun doSomeWork() {
        coroutineScope.launch { suspendFunciton1() }
        coroutineScope.launch { suspendFunciton2() }
    }
Can anyone confirm that I'm correct about these statements (or am I somehow reading my test results wrong) 1. If I start
doSomeWork
with
viewModelScope
(for example), then
suspendFunction1()
and
2()
will both be started with that same
viewModelScope
to initiate
doSomeWork
2.
suspendFunction1()
and
2()
will run in parrallel 3.
doSomeWork
won't complete until
suspendFunction1()
and
2()
complete
d

Dominaezzz

05/20/2023, 2:10 PM
Statement 3 is wrong
e

ephemient

05/20/2023, 2:10 PM
2. viewModelScope
uses Dispatchers.Main.immediate
which is a single thread 3. no, using another scope breaks the connection with the current one
d

Dominaezzz

05/20/2023, 2:10 PM
Statement 2 is correct, if you say concurrent instead of parallel.
c

Colton Idle

05/20/2023, 2:13 PM
Do any of your responses change if I say that suspendFunction1 and 2 are retrofit networking calls. So they will run off the main thread. 😄 ?
e

ephemient

05/20/2023, 2:14 PM
assuming you're not using blocking retrofit calls, 2 changes. but not 3
c

Colton Idle

05/20/2023, 2:14 PM
Re: 3 Damn. back to the drawing board then. How would I get this to be playing within the bounds of structured concurrency?
just?
suspend fun doSomeWork() {
        viewModelScope.launch { suspendFunciton1() }
        viewModelScope.launch { suspendFunciton2() }
    }
d

Dominaezzz

05/20/2023, 2:15 PM
Easy.
coroutineScope { launch {...}; launch {...}}
c

Colton Idle

05/20/2023, 2:19 PM
@Dominaezzz isn't that essentially what I already have?
oh jeeeeez. i copy pastad the wrong code.
🤦
this is what i meant to have put here If I have this suspend function
suspend fun doSomeWork() {
        coroutineScope { launch { suspendFunciton1() } }
        coroutineScope { launch { suspendFunciton2() } }
    }
Can anyone confirm that I'm correct about these statements (or am I somehow reading my test results wrong) 1. If I start
doSomeWork
with
viewModelScope
(for example), then
suspendFunction1()
and
2()
will both be started with that same
viewModelScope
to initiate
doSomeWork
2.
suspendFunction1()
and
2()
will run in parrallel 3.
doSomeWork
won't complete until
suspendFunction1()
and
2()
complete
e

ephemient

05/20/2023, 2:21 PM
2 is wrong for a different reason now
d

Dominaezzz

05/20/2023, 2:21 PM
(I need to go, I'll leave the rest to ephemient 🙂)
e

ephemient

05/20/2023, 2:22 PM
the first call to coroutineScope will not resume the next statement in doSomeWork until the launch inside has completed
what you've written there is equivalent to
suspend fun doSomeWork() {
    suspendFunction1()
    suspendFunction2()
}
just with some extra machinery
c

Colton Idle

05/20/2023, 2:52 PM
isn't that ^ just "procedural" code though?
I guess my real goal is to just have doSomeWork call two network calls so they run in parrallel. lol
e

ephemient

05/20/2023, 2:53 PM
launch them from the same child scope then
with two scopes, you're just writing sequential code in a more confusing way
c

Colton Idle

05/20/2023, 2:55 PM
So just what Dominic said?
coroutineScope { launch {...}; launch {...}}
?
I swear with coroutines I feel like I have it figured out, then I'm met with something simple like this and i feel like an idiot. 🤦
p

Patrick Steiger

05/20/2023, 2:57 PM
If you want to do parallel decomposition of work , opening a new scope and launching coroutines inside it is the way to go
Because a scope returns only when all children finish, the enclosing suspend fun will also only finish when scope returns (hence when all children finish)
e

ephemient

05/20/2023, 2:58 PM
strictly speaking, launch gives you concurrency, not necessarily parallelism; that may (optionally) come from the dispatcher
p

Patrick Steiger

05/20/2023, 2:58 PM
There are a number of coroutine scope builders like
coroutineScope {}
,
supervisorScope
or
withContext
.
c

Colton Idle

05/20/2023, 2:59 PM
Alright. So this is more like what I'm after?.
suspend fun doSomeWork() {
        coroutineScope { 
          launch { suspendFunciton1() }
          launch { suspendFunciton2() }
        }
    }
p

Patrick Steiger

05/20/2023, 3:01 PM
@ephemient I think the opposite is true. When you are under a single threaded dispatcher, you get coroutines running in parallel (while one has not finished yet, the other might start running), but not at the same time ( there is only one thread available) But don’t quote me on that, my brains always go shenanigans when concurrent vs parallel differences and I might be exchanging those terms
c

Colton Idle

05/20/2023, 3:01 PM
And so something like this would be bad because I'm breaking structured concurrency?
suspend fun doSomeWork() {
        viewModelScope.launch { 
          launch { suspendFunciton1() }
          launch { suspendFunciton2() }
        }
    }
e

ephemient

05/20/2023, 3:02 PM
I often write
suspend fun foo() = coroutineScope {
since it's a relatively common pattern to want a scope with the same "lifetime" as the function call
p

Patrick Steiger

05/20/2023, 3:03 PM
@Colton Idle yes, the last snipped you sent is “bad” because the suspend fun will return without waiting for the launched coroutines to finish
e

ephemient

05/20/2023, 3:03 PM
coroutines interleaving with each other at suspend points on a single thread dispatcher is concurrency, not parallelism
p

Patrick Steiger

05/20/2023, 3:03 PM
So parallel coroutines require 2+ threads
Then I suppose Kdoc of
coroutineScope
is wrong because it talks about parallel decomposition of work
c

Colton Idle

05/20/2023, 4:03 PM
thanks everyone for the saturday convo! learn something new every day. 😄
oh. one last thing. in
suspend fun doSomeWork() {
        coroutineScope { 
          launch { suspendFunciton1() }
          launch { suspendFunciton2() }
        }
    }
the two inner suspend functions will be cancelled if the outer syspend function is cancelled right? in other words. if i initially called doSomeWork with viewModelScope then the two inner suspend are called with that same "scope", no?
p

Patrick Steiger

05/20/2023, 4:30 PM
Yes. When you open a scope with
coroutineScope
, the job of this scope is a child of the job of the enclosing suspend fun
And the job of the inner launches are children of the
coroutineScope{}
job
c

Colton Idle

05/20/2023, 4:40 PM
okay. bear with me. one last question. if this happens in "parallel"
suspend fun doSomeWork() {
        coroutineScope { 
          launch { suspendFunciton1() }
          launch { suspendFunciton2() }
        }
    }
why wouldn't this be in parallel
suspend fun doSomeWork() {
        coroutineScope { 
          launch { suspendFunciton1() }
        }
        coroutineScope { 
          launch { suspendFunciton2() }
        }
    }
oh yeah. because like Patrick said. only once a coroutineScope is completely done with its work does it return.
okay. i think i get it now
p

Patrick Steiger

05/20/2023, 4:43 PM
Yes. To get more technical, once the work immediately on the coroutineScope lambda gets done, it’s job moves it’s state to Completing if it still has children jobs doing work (Active). Once all children move to Completed, then the coroutineScope job moves from Completing to Completed, and only then it returns
Job lifecycle is the key to understand all that’s going on in there (structured concurrency)
“Completing” means “I’m done, but my children are not”
“Completed” means “me and my children are done”
Reading JobSupport KDoc is really helpful
c

Colton Idle

05/20/2023, 7:06 PM
Now another variation of my question. if I have
suspend fun doSomeWork() {
        coroutineScope { 
          launch { suspendFunciton1() }
          launch { suspendFunciton2() }
        }
    }
how would I now be able to use their returns, once both of them return. This works for me, but curiuos if theres some other standard way.
suspend fun doSomeWork(): Pair<MyType, OtherType> {
        var one: MyType? = null
        var two: OtherType? = null
        coroutineScope { 
          launch { one = suspendFunciton1() }
          launch { two = suspendFunciton2() }
        }
        return Pair(one, two)
    }
p

Patrick Steiger

05/20/2023, 7:08 PM
suspend fun doSomeWork() = 
  coroutineScope {
    val one = async { susFun1() }
    val two = async { susFun2() }
    one.await() to two.await()
  }
d

Dominaezzz

05/20/2023, 7:09 PM
suspend fun doSomeWork(): Pair<MyType, OtherType> {
        coroutineScope { 
          val one = async { suspendFunciton1() }
          val two = async { suspendFunciton2() }
          Pair(one.await(), two.await())
        }
    }
c

Colton Idle

05/20/2023, 11:07 PM
danke
e

ephemient

05/20/2023, 11:18 PM
as there are appropriate contracts on the scope builders,
val first: Type1
val second: Type2
coroutineScope {
    val firstAsync = async { suspendFunction1() }
    val second Async = async { suspendFunction2() }
    first = firstAsync.await()
    second = secondAsync.await()
}
// first and second are now initialized
works. destructuring an intermediate data structure is easier if an appropriate one exists, but this is an alternative if you have many different types