Dispatchers used with CoroutineScope() factory function - kotlin

I'm playing with coroutines and I've tried to run the following code:
private val myScope = CoroutineScope(CoroutineName("my own coroutine"))
fun destroy() {
myScope.cancel()
}
fun main() {
myScope.launch {
println("I'm working in thread ${Thread.currentThread().name}")
}
destroy()
}
And surprisingly for me, the output that I've got was the following:
I'm working in thread DefaultDispatcher-worker-1 #my own coroutine#1
I'm wondering where did DefaultDispatcher come from... Because I didn't specify Dispatcher explicitly.

If you dig into the source you'll find out the following:
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyStandaloneCoroutine(newContext, block) else
StandaloneCoroutine(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}
public actual fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext {
val combined = coroutineContext + context
val debug = if (DEBUG) combined + CoroutineId(COROUTINE_ID.incrementAndGet()) else combined
return if (combined !== Dispatchers.Default && combined[ContinuationInterceptor] == null)
debug + Dispatchers.Default else debug
}
Since you didn't pass a CoroutineDispatcher to the scope itself coroutineContext doesn't have it and you did not pass any CoroutineDispatcher to the launch, context is equal to EmptyCoroutineContext. If you're not debugging, debug equals to combined and combined is not holding the reference to Dispatchers.Default and combined[ContinuationInterceptor] is null, since CoroutineDispatcher inherits ContinuationInterceptor and none is provided, therefore the final returned context is equal to debug (CoroutineName + Job() (provided by CoroutineScope() method, since you didn't provide it yourself) + EmptyCoroutineContext] + Dispatchers.Default.

Related

Suspend function inside flow kotlin

So I have the below kotlin code with flows and suspend function.
suspend fun doWork(): Int {
// do some time intensive task
return if (task.isSuccessful){1}else{-1}
}
suspend fun sendTaskStatus(status:Int): Boolean {
// send this status data to the server via internet
return task.isSuccessful()
}
fun listenForCompletion() = flow <String> {
val isTaskCompleted = false
val taskStatus = doWork()
if (taskStaus == 1){
val isDataSent = sendTaskStatus()
if(isDataSent)
isTaskCompleted = true
}
emit(isTaskCompleted)
}
Now my question is if I collect listenForCompletion() function from a coroutine, what will be the sequence of execution of this code? If this isn't correct what would be the correct way of doing this assuming that flow and suspend functions are preserved?

Kotlin Coroutines: what's the diffrence between using NonCancellable and a standalone new Job

In Coroutines, when I want to guard a block of code against cancellation, I should add NonCancellable to the Context:
#Test
fun coroutineCancellation_NonCancellable() {
runBlocking {
val scopeJob = Job()
val scope = CoroutineScope(scopeJob + Dispatchers.Default + CoroutineName("outer scope"))
val launchJob = scope.launch(CoroutineName("cancelled coroutine")) {
launch (CoroutineName("nested coroutine")) {
withContext(NonCancellable) {
delay(1000)
}
}
}
scope.launch {
delay(100)
launchJob.cancel()
}
launchJob.join()
}
}
The above unit test will take ~1.1sec to execute, even though the long-running Coroutine is cancelled after just 100ms. That's the effect of NonCancellable and I understand this point.
However, the below code seems to be functionally equivalent:
#Test
fun coroutineCancellation_newJobInsteadOfNonCancellable() {
runBlocking {
val scopeJob = Job()
val scope = CoroutineScope(scopeJob + Dispatchers.Default + CoroutineName("outer scope"))
val launchJob = scope.launch(CoroutineName("cancelled coroutine")) {
launch (CoroutineName("nested coroutine")) {
withContext(Job()) {
delay(1000)
}
}
}
scope.launch {
delay(100)
launchJob.cancel()
}
launchJob.join()
}
}
I tried to find any functional differences between these two approaches in terms of cancellation, error handling and general functionality, but so far I found none. Currently, it looks like NonCancellable is in the framework just for readability.
Now, readability is important, so I'd prefer to use NonCancellable in code. However, its documentation makes it sound like it is, in fact, somehow different from a regular Job, so I want to understand this aspect in details.
So, my quesiton is: is there any functional difference between these two approaches (i.e. how can I modify these unit tests to have difference in outcomes)?
Edit:
Following Louis's answer I tested "making cleanup non-cancellable" scenario and in this case Job() also works analogous to NonCancellable. In the below example, unit test will run for more than 1sec, even though the coroutine is cancelled just after 200ms:
#Test
fun coroutineCancellation_jobInsteadOfNonCancellableInCleanup() {
runBlocking {
val scope = CoroutineScope(Job() + Dispatchers.Default + CoroutineName("outer scope"))
val launchJob = scope.launch(CoroutineName("test coroutine")) {
try {
delay(100)
throw java.lang.RuntimeException()
} catch (e: Exception) {
withContext(Job()) {
cleanup()
}
}
}
scope.launch {
delay(200)
launchJob.cancel()
}
launchJob.join()
}
}
private suspend fun cleanup() {
delay(1000)
}
NonCancellable doesn't respond to cancellation, while Job() does.
NonCancellable implements Job in a custom way, and it doesn't have the same behavior as Job() that is using cancellable implementation.
cancel() on NonCancellable is no-op, unlike for Job() where it would cancel any child coroutine, and where any crash in the child coroutines would propagate to that parent Job.

What is the lifetime kotlinx.coroutines.coroutineScope in Kotlin?

The following code are from the project architecture samples at https://github.com/android/architecture-samples
What is the lifetime kotlinx.coroutines.coroutineScope in Kotlin? Will this function saveTask return as soon as the given block and all its children coroutines are completed?
If I pass a ViewModel.viewModelScope to DefaultTasksRepository instead of kotlinx.coroutines.coroutineScope, what are differents ?
BTW, it seems that the Code A don't pass any object of CoroutineScope, why?
Code A
import kotlinx.coroutines.coroutineScope
...
class DefaultTasksRepository(
private val tasksRemoteDataSource: TasksDataSource,
private val tasksLocalDataSource: TasksDataSource,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) : TasksRepository {
...
override suspend fun saveTask(task: Task) {
coroutineScope {
launch { tasksRemoteDataSource.saveTask(task) }
launch { tasksLocalDataSource.saveTask(task) }
}
}
...
}
Code B
object ServiceLocator {
private val lock = Any()
private var database: ToDoDatabase? = null
...
private fun createTasksRepository(context: Context): TasksRepository {
val newRepo = DefaultTasksRepository(FakeTasksRemoteDataSource, createTaskLocalDataSource(context))
tasksRepository = newRepo
return newRepo
}
...
}
Added content
To Animesh Sahu: Thanks!
Are you sure that "A coroutineScope is a factory function that creates a CoroutineScope" , the following code is source code, it seems that the return value is not the object of CoroutineScope.
Source Code
public suspend fun <R> coroutineScope(block: suspend CoroutineScope.() -> R): R =
suspendCoroutineUninterceptedOrReturn { uCont ->
val coroutine = ScopeCoroutine(uCont.context, uCont)
coroutine.startUndispatchedOrReturn(coroutine, block)
}
A coroutineScope is a factory function that creates a CoroutineScope with the same context as it was called with but overriding the Job of that context.
CoroutineScope has lifetime until it is cancelled by calling cancel() on it or calling cancel() on CoroutineScope.coroutineContext or explicitly calling on the attached job coroutineContext[Job].cancel().
a coroutineScope is just a wrapper that creates immediate CoroutineScope that cancels itself up after executing its childrens.
PS: coroutineScope function is used for parallel decomposition of tasks with a new Job instance for control over its children

Second async method is not executed from specified scope if first execution threw exception

I am trying to achieve is to have UI, IO and DEFAULT CoroutineScope within my BaseActivity activity. Then use these scopes for some operations.
Everything seems to work fine unless exception is thrown inside suspendCoroutine {}.
If no exception is thrown I can use ioScope multiple times without no problems. But if exception is thrown in Response.ErrorListener and I call loginAsync(), async won't be executed and coroutine will stuck at userDeferred.await().
I checked ioScope.isActive flag. Before Exception was thrown flag was set to true. After exception was thrown flag is set to false and I can exception in was thrown in scope.
I found that when I use instead of ioScope.async{ } function withContext(ioScope.coroutineContext){} exception will not break scope and it can be used again.
Could anyone please help me to resolve this issue. I could not find any help in documentations nor in blogs.
BaseActivity
CoroutineScopes creation.
abstract class BaseActivity : AppCompatActivity() {
private val ioJob = Job()
private val defaultJob = Job()
private val uiJob = Job()
protected val ioScope: CoroutineScope
get() = CoroutineScope(Dispatchers.IO + ioJob)
protected val uiScope: CoroutineScope
get() = CoroutineScope(Dispatchers.Main + uiJob)
protected val defaultScope: CoroutineScope
get() = CoroutineScope(Dispatchers.Default + defaultJob)
final override fun finish() {
super.finish()
uiJob.cancel()
defaultJob.cancel()
ioJob.cancel()
getActivityTransitions().setFinishActivityTransition(this)
}
}
UserRepository
Usage of ioScope from BaseActivity.
#Throws(LoginException::class)
suspend fun loginAsync(loginData: LoginData, context: Context): Deferred<User> {
return ioScope.async {
suspendCoroutine<User> { continuation ->
val jsonObjectRequest = HttpClient.createJsonObjectRequest(
"/users/me2",
loginData.toJsonString(),
Response.Listener {
val httpResponse : HttpResponse<User> = it.toString().jsonToObject()
continuation.resume(httpResponse.response)
},
Response.ErrorListener {
continuation.resumeWithException(LoginException(it))
}
)
HttpClient.getInstance(context).addToRequestQueue(jsonObjectRequest)
}
}
}
LoginActivity
private suspend fun performLogin() {
val loginData = LoginData(login_username_text_input.value.toString(), login_password_text_input.value.toString())
val userDeferred = UserServerRepository(ioScope).loginAsync(loginData,this#LoginActivity);
try {
val result = userDeferred.await()
login_username_text_input.value = result.company
//HomeActivity.startActivity(this#LoginActivity)
//finish()
}catch (loginException: LoginException){
login_username_text_input.value = loginException.message
}
}
LoginActivity button setup
loginButton.main_button.setAsyncSafeOnClickListener(uiScope, suspend {
performLogin()
})
setAsyncSafeOnClickListener implemetation
fun View.setAsyncSafeOnClickListener(uiScope: CoroutineScope, action: suspend () -> Unit) {
val safeClickListener = SafeClickListener {
uiScope.launch {
isEnabled = false
action()
isEnabled = true
}
}
setOnClickListener(safeClickListener)
}
Short answer: you have to use SupervisorJob() instead of Job() if you want to have robust scopes.
Long answer: here is great article about how error handling in coroutine scopes works https://proandroiddev.com/kotlin-coroutine-job-hierarchy-finish-cancel-and-fail-2d3d42a768a9

Avoid cancelling of parent job on exception on child coroutine

I'm experimenting with handling exceptions in Kotlin coroutines on Android.
My use case is I want to do bunch of tasks on background(in async manner) and update multiple UI components on a single activity.
I've designed a BaseActivity structure to implement CoroutineScope so I can couple couroutines invoked with the lifecycle of activity.
Also, I've a Repository class which handles network calls.
I've achieved running multiple tasks concurrently. I know if I use a single Job object to cancel all coroutines on onDestroy() of the activity and doing (launch) multiple coroutines in activity, Exception in any single coroutine will cancel the Job from its CoroutineContext. And since Job is attached to lifecycle of activity, it will cancel all other coroutines too.
I've tried using a CoroutineExceptionHandler. It catches exception but cancels the Job too. Which in result cancels all other coroutines.
What I want?
Be able to use single Job object to attach with activity lifecycle
Exception in one coroutine shouldn't cancel other coroutines
Adding code below
class BaseActivity : AppCompatActivity(), CoroutineScope {
val job = Job()
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
launch(coroutineContext) {
Log.i("GURU", "launch1 -> start")
val result1Deferred = async { Repository().getData(1) }
val result2Deferred = async { Repository().getData(2) }
Log.i("GURU", "awaited result1 = " + result1Deferred.await() + result2Deferred.await())
}
//If Exception is Thrown, Launch1 should still continue to complete
advancedLaunch(coroutineContext) {
Log.i("GURU", "launch2 -> start")
val result1Deferred = async { Repository().getData(3) }
val result2Deferred = async { Repository().getData(4) }
delay(200)
throw Exception("Exception from launch 2")
Log.i("GURU", "awaited result2 = " + result1Deferred.await() + result2Deferred.await())
}
}
fun CoroutineScope.advancedLaunch(context: CoroutineContext = EmptyCoroutineContext,
exceptionBlock: (Throwable) -> Unit = {Log.i("GURU", it.message)},
launchBlock: suspend CoroutineScope.() -> Unit) {
val exceptionHandler = CoroutineExceptionHandler { _, throwable -> exceptionBlock(throwable)}
launch(context + exceptionHandler) { launchBlock() }
}
override fun onDestroy() {
super.onDestroy()
job.cancel()
Log.i("GURU", "job -> cancelled")
}
}
The Log Result of this is
I/GURU: launch1 -> start
I/GURU: launch2 -> start
I/GURU: getData -> start 1
I/GURU: getData -> start 2
I/GURU: getData -> start 4
I/GURU: getData -> start 3
I/GURU: Exception from launch 2
--------- beginning of crash
You might want to replace your Job with SupervisorJob.
It prevents exceptions from propagating "upwards" (one failed child will not cause entire job to fail), but still allows you to push cancellation "downwards" (to running children).