Skip to content

Instantly share code, notes, and snippets.

@Audhil
Created February 18, 2022 11:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Audhil/73066d2e10f5021805cc2a798305409a to your computer and use it in GitHub Desktop.
Save Audhil/73066d2e10f5021805cc2a798305409a to your computer and use it in GitHub Desktop.
5 coroutine mistakes to avoid
package com.bhnetwork.scanit.consumer
import kotlinx.coroutines.*
import kotlin.random.Random
// mistake 1 - getFirstName call happens one after another
suspend fun getUserFirstNames(userIds: List<String>): List<String> {
val firstNames = mutableListOf<String>()
userIds.forEach {
firstNames.add(getFirstName(it))
}
return firstNames
}
// api call
suspend fun getFirstName(userId: String): String {
delay(1000L)
return "FirstName"
}
// solution - all getFirstName call happens at the same time
suspend fun getUserFirstNamesSoln(userIds: List<String>): List<String> {
val firstNames = mutableListOf<Deferred<String>>()
coroutineScope {
userIds.forEach {
val firstName = async { getFirstName(it) }
firstNames.add(firstName)
}
}
return firstNames.awaitAll()
}
////////////////////////////////////////////////////////////////////////////////////////
// mistake 2 - coroutine cancellation is not propagated to coroutine itself
suspend fun doSomething() {
val job = CoroutineScope(Dispatchers.Default).launch {
var randNum = Random.nextInt(100_000)
while (randNum != 50000) {
randNum = Random.nextInt(100_000)
}
}
delay(500)
job.cancel() // trying to cancel the coroutine, but still coroutine won't know, it'll be busy in randNum generation
}
// solution1 - is it active?
suspend fun doSomethingSoln1() {
val job = CoroutineScope(Dispatchers.Default).launch {
var randNum = Random.nextInt(100_000)
while (randNum != 50000 && isActive) {
randNum = Random.nextInt(100_000)
}
}
delay(500)
job.cancel()
}
// solution2 - ensureActive
suspend fun doSomethingSoln2() {
val job = CoroutineScope(Dispatchers.Default).launch {
var randNum = Random.nextInt(100_000)
while (randNum != 50000) {
randNum = Random.nextInt(100_000)
ensureActive()
}
}
delay(500)
job.cancel()
}
////////////////////////////////////////////////////////////////////////////////////////
// mistake 3 - coroutine is not main thread safe
suspend fun doSomeWork(): Result<String> {
val result = doNetworkCall()
return if (result == "Success")
Result.success(result)
else
Result.failure(Exception())
}
// this method is not main thread safe
suspend fun doNetworkCall(): String {
delay(3000L)
return if (Random.nextBoolean())
"Success"
else
"failed"
}
// solution - this method is always main thread safe(always it'll be executed in IO thread)
suspend fun doNetworkCallSoln(): String {
return withContext(Dispatchers.IO) {
delay(3000L)
if (Random.nextBoolean())
"Success"
else
"failed"
}
}
////////////////////////////////////////////////////////////////////////////////////////
// mistake 4 - cancellation exception is not propagated to parent scope
suspend fun riskyTask() {
try {
delay(1000)
println("yup: the answer is ${10 / 0}")
} catch (e: Exception) {
// all the exceptions will go here, it'll not be propagated to parent scope
println("something went wrong!")
}
}
// solution - manually throw the exception in the method
suspend fun riskyTaskSoln() {
try {
delay(1000)
println("yup: the answer is ${10 / 0}")
} catch (e: Exception) {
if(e is CancellationException){ // for eg., let us assume 10/0 throws cancellation exception
throw e
}
println("something went wrong!")
}
}
////////////////////////////////////////////////////////////////////////////////////////
// mistake 5 - choose lifeCycleScope or viewmodelScope for api calls based on your needs
////////////////////////////////////////////////////////////////////////////////////////
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment