I am still not quite sure when I should be using `...
# coroutines
d
I am still not quite sure when I should be using
fun foo(): Deferred<Foo>
vs.
suspend fun foo(): Foo
. The code must execute on a specific dispatcher, so I could use
withContext
for the
suspend
version. It feels to me like
suspend
is the cleaner variant. Correct?
d
Yes, but you might want that class to receive a CoroutineContext in its constructor just in case you need to switch it for testing or something. Deferred is usually when you run it with
async {}
and only need the result later
Also, it's better to keep it suspend and use async at calling site, so you can opt in to concurrency, or use it without when needed. Also the scope you run from is usually at the top level (like in the ui or viewmodel/presenter), and you generally need one for async anyways
d
Consider making it an extension function of
CoroutineScope
Or use suspend version
Also, I think
withContext
has little cost when the thread is already the target
d
The extension function isn't so nice if its not in the same class... since there's two receivers you need a
with
on one of them
But this is all only for blocking api, to wrap callbacks all this isn't usually needed of course...
s
@dave08 is switching the context for testing to make launched coroutines as blocking?
d
There's Dispatchers.Unconfined or you can turn something running on a thread pool to run on a singlr thread instead. You still need to test concurrency, bit you can simplify things when its not needed.
That's one of the reasons I find it better to write everything in repositories etc as simple suspends without concurrency (or at least with a replaceable context), and have real concurrency in the main class with the context, it makes testing simple in all the other classes @Sam
s
Okay, in my case i'm using Unconfined. I was actually hoping to avoid calling .join() from runBlocking in my test
Copy code
override fun onButtonClicked() = viewModelScope.launch {
        doSomething() // suspend function
    }
viewModelScope is built using the dispatcher passed via constructor (Main in case of application and Unconfined in case of tests)
In tests, i have
Copy code
runBlocking {
            viewModel.onButtonClicked().join()
            verify( ...)
        }
Is there a way to avoid this join?
d
Can you pass runBlocking's scope? That should block the viewmodelscope...
s
I thought of doing that but in a sample it didn't work as expected
Copy code
runBlocking {

        val dispatcher1 : CoroutineDispatcher = coroutineContext[ContinuationInterceptor] as CoroutineDispatcher

        launch(dispatcher1) {

            delay( 1000 )
            println( "first finished" )
        }

        println( "outer finished" )

    }
in this case, even though the dispatcher is passed to launch builder, outer finished is processed on delay
d
The
it
in runBlocking is its scope...
If you create your viewmodel instance inside it, you can technically just pass
it
as the scope, no?
s
Okay, instead of a dispatcher, i could pass in an entire scope
but even then, isn't that the same as this?
Copy code
runBlocking {

        launch {

            delay( 1000 )
            println( "first finished" )
        }

        println( "outer finished" )

    }
d
In that case, you need to use runBlocking scope + another dispatcher like io
That way you get the join automatically, but it runs the launch async
And the coroutines get cancelled with their runBlocking parent
Or just leave the scope in the viewmodel and use its job to join to runBlocking and cancel at the end...
Maybe even make a little extension function to reuse in all your tests for this
s
I didn't get the IO dispatcher part, it still doesn't change the behavior
Copy code
runBlocking {

        val scope = CoroutineScope( coroutineContext + <http://Dispatchers.IO|Dispatchers.IO> )

        scope.launch {

            delay( 1000 )
            println( "first finished" )
        }

        println( "outer finished" )

    }
Am i missing something?
waiting on the launched coroutine with join() in runBlocking does work. It just forces a Job return type on my view model methods. I was hoping to avoid that.
d
Basically I believe you need to use runBlocking's Job in the new scope, then the launch will become its child... I'm not sure why that shouldn't work... maybe use
it
instead of
coroutineContext
? I can't test it out i'm not on a computer now 🙂
s
no worries. Actually, runBlocking gets a this, which is a coroutinecontext
Tried getting the job from coroutinecontext and used that to build the scope but still the same behavior
I guess my problem statement, is there a way to enforce sequential execution of all child coroutines from a runBlocking
g
regarding the original question @vnsgbt I think this is actually one of kotlins masterstrokes: always use `suspend `if you can, because it keeps everything sequential by default, which is probably the best way to start writing concurrent systems. I was just recently burned by a couple
public async Task doStuff()
in C#, where I forgot to
await
the return value because its effectively void
, and I got the resulting typical concurrent heisenbugs.
The strategy in kotlin of just "lets assume its synchronous" is a good one, but that strategy only really helps if you use
suspend fun asdf(): R
by default instead of
fun asdfAsync(): SomeJobType<R>
as your default way of thinking
d
Wow, you go on a party for new years and you come back to this, thanks guys, I'll read the thread now 😄
😁 1
@groostav That makes sense and it does indeed produce the cleanest code.
suspend
it is.
d
Roman spoke about this a bit in the recent webinar on coroutines @diesieben07
d
Thanks, I was meaning to check that out. Thank you for reminding me