Minimally what should I add to build.gradle.kts to...
# getting-started
p
Minimally what should I add to build.gradle.kts to use
kotlinx.coroutines.async
? I have now:
Copy code
dependencies {
implementation ("org.jetbrains.kotlin:kotlin-stdlib")
implementation ("org.jetbrains.kotlin:kotlin-reflect")


implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:$coroutinesVersion")
j
The dependency on
kotlinx-coroutines-core
is enough
Also, unless you do something special, you don't need an explicit dependency on the
kotlin-stdlib
And if you're using a version of coroutines >= 1.7.0, you don't even need the
kotlinx-coroutines-jdk8
dependency (which was merged with the core)
As for
kotlin-reflect
, it depends on whether something else in your project needs it
p
Hm, I still get
Copy code
Unresolved reference: async
for the line
Copy code
val auctionAssetInfoDeferred = kotlinx.coroutines.async {
when I do a
gradle clean build
So in theory, the following should be enough:
Copy code
val coroutinesVersion = "1.7.0"
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
👌 1
j
async
cannot be used this way, though. If you check the documentation of this function, you'll see that it requires a
CoroutineScope
as receiver. You need to add an
import kotlinx.coroutines.async
at the top of your file, and then just use
async { ... }
(unprefixed) in a place where a
CoroutineScope
is in context, or
scope.async { ... }
where
scope
is a
CoroutineScope
. Using a fully qualified name with the
kotlinx.coroutines.
prefix doesn't work if the function expects a receiver (I guess that's a syntax limitation).
p
Thanks. Well, then maybe I do not need this complexity to parallelize 2-3 tasks?
j
It's usually quite simple to express concurrent/parallel tasks with coroutines:
Copy code
suspend fun runTasksInParallel(): SomeResult = coroutineScope {
    val deferredResult1 = async { task1() }
    val deferredResult2 = async { task2() }
    SomeResult(deferredResult1.await(), deferredResult2.await())
}
Or if you're not using coroutines anywhere else and you want a blocking call rather than a suspend function:
Copy code
fun runTasksInParallel(): SomeResult = runBlocking(Dispatchers.Default) {
    val deferredResult1 = async { task1() }
    val deferredResult2 = async { task2() }
    SomeResult(deferredResult1.await(), deferredResult2.await())
}
Or use
launch
instead of
async
if you don't need the results:
Copy code
fun runTasksInParallel() = runBlocking(Dispatchers.Default) {
    launch { task1() }
    launch { task2() }
}
p
Well, I fire up 3 network calls, and wait for all of them.
j
What networking library are you using? Are you using something that supports suspend functions like Ktor client? If that's the case, then it's almost necessary to use coroutines (and advised anyway). If your client is blocking, it's still possible. Just make sure you use
<http://Dispatchers.IO|Dispatchers.IO>
p
Maybe some day I will need the flexibility of that, but currently I'm just searching a quick wait to do something similar to typescript's (nodejs's) async things. Is there a docs like kotlin async for dummies typescript devs?
j
The thing is, Kotlin is multithreaded unlike the JS environment, so there are more things that can go wrong if not handled properly. Also, TypeScript's approach to async/await doesn't have any structured concurrency, so you can leak async work easily. It is equivalent to using
GlobalScope.async { ... }
and
deferred.await()
in Kotlin (I had written this comparison a while ago if that helps you). Note that using
GlobalScope
is regarded as a bad idea in Kotlin because of this problem. It escapes the safety of structured concurrency. You basically start coroutines flying around without making sure they don't hang (and thus leak) forever, or this kind of things.
That's why we use constructs like
coroutineScope { ... }
to give some sort of lifetime to the async work we start
p
Thanks, I will read it. So having a GlobalScope is something like a global lock? I have some java background in the far past. So, to just parallelize one function, your second approach is a good start
Copy code
fun runTasksInParallel(): SomeResult = runBlocking(Dispatchers.Default) {
    val deferredResult1 = async { task1() }
    val deferredResult2 = async { task2() }
    SomeResult(deferredResult1.await(), deferredResult2.await())
}
j
So having a GlobalScope is something like a global lock?
Not really, it doesn't help synchronizing anything actually. It's more of a "no scope" equivalent. A scope in Kotlin coroutines can be seen as a way to delimit the lifetime of the coroutines that you launch in it (it acts as a parent, and allows to cancel all child tasks in bulk for instance). The
GlobalScope
doesn't act as a parent, it doesn't keep track of children, it doesn't allow to cancel children in bulk. It's sort of an escape hatch for very narrow use cases.
So, to just parallelize one function, your second approach is a good start
Yeah that is a good start.
runBlocking
basically blocks the calling thread while waiting for all child coroutines to finish, and provides a coroutine scope that delimits their lifetime. You know that, when this
runBlocking
call returns, all child coroutines have completed in some way (and thus cannot leak).
coroutineScope
is the equivalent of
runBlocking
, but it suspends the calling coroutine instead of blocking the calling thread. So it requires you to be already inside the coroutines world (because it's marked as
suspend
itself).
It's hard to explain coroutines in such brief sentences, but I hope you get the gist of it. There are plenty of resources that may help you with it if you need. And you can always ask specific questions on stack overflow or here in #coroutines if you want to clear something up.
p
Oh, thank you, I haven't noticed there is a separate channel. But maybe my entry level in that area justifies it to be in getting-started.
j
#getting-started is a good place to ask questions anyway, especially basic. People will redirect in case there is a more specific channel 😉
j
You might run across some old blog posts that show code that calls coroutine launchers like
async
without a
CoroutineScope
receiver. This was before the API became stable and requires this to enforce structure concurrency. So if you really have a use case to have your async work run with an unbounded scope, you explicitly use
GlobalScope
to declare this intent. I recommend reading Roman's blog post about structured concurrency. He has many good posts on the topic of coroutines: https://elizarov.medium.com/
🙏 1
1