Sanity check time! If I have this suspend function...
# coroutines
c
Sanity check time! If I have this suspend function
Copy code
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
Statement 3 is wrong
e
2. viewModelScope
uses Dispatchers.Main.immediate
which is a single thread 3. no, using another scope breaks the connection with the current one
d
Statement 2 is correct, if you say concurrent instead of parallel.
c
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
assuming you're not using blocking retrofit calls, 2 changes. but not 3
c
Re: 3 Damn. back to the drawing board then. How would I get this to be playing within the bounds of structured concurrency?
just?
Copy code
suspend fun doSomeWork() {
        viewModelScope.launch { suspendFunciton1() }
        viewModelScope.launch { suspendFunciton2() }
    }
d
Easy.
coroutineScope { launch {...}; launch {...}}
c
@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
Copy code
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
2 is wrong for a different reason now
d
(I need to go, I'll leave the rest to ephemient šŸ™‚)
e
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
Copy code
suspend fun doSomeWork() {
    suspendFunction1()
    suspendFunction2()
}
just with some extra machinery
c
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
launch them from the same child scope then
with two scopes, you're just writing sequential code in a more confusing way
c
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
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
strictly speaking, launch gives you concurrency, not necessarily parallelism; that may (optionally) come from the dispatcher
p
There are a number of coroutine scope builders like
coroutineScope {}
,
supervisorScope
or
withContext
.
c
Alright. So this is more like what I'm after?.
Copy code
suspend fun doSomeWork() {
        coroutineScope { 
          launch { suspendFunciton1() }
          launch { suspendFunciton2() }
        }
    }
p
@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
And so something like this would be bad because I'm breaking structured concurrency?
Copy code
suspend fun doSomeWork() {
        viewModelScope.launch { 
          launch { suspendFunciton1() }
          launch { suspendFunciton2() }
        }
    }
e
I often write
Copy code
suspend fun foo() = coroutineScope {
since it's a relatively common pattern to want a scope with the same "lifetime" as the function call
p
@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
coroutines interleaving with each other at suspend points on a single thread dispatcher is concurrency, not parallelism
p
So parallel coroutines require 2+ threads
Then I suppose Kdoc of
coroutineScope
is wrong because it talks about parallel decomposition of work
c
thanks everyone for the saturday convo! learn something new every day. šŸ˜„
oh. one last thing. in
Copy code
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
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
okay. bear with me. one last question. if this happens in "parallel"
Copy code
suspend fun doSomeWork() {
        coroutineScope { 
          launch { suspendFunciton1() }
          launch { suspendFunciton2() }
        }
    }
why wouldn't this be in parallel
Copy code
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
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
Now another variation of my question. if I have
Copy code
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.
Copy code
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
Copy code
suspend fun doSomeWork() = 
  coroutineScope {
    val one = async { susFun1() }
    val two = async { susFun2() }
    one.await() to two.await()
  }
d
Copy code
suspend fun doSomeWork(): Pair<MyType, OtherType> {
        coroutineScope { 
          val one = async { suspendFunciton1() }
          val two = async { suspendFunciton2() }
          Pair(one.await(), two.await())
        }
    }
c
danke
e
as there are appropriate contracts on the scope builders,
Copy code
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