HelloCW
HelloCW

Reputation: 2285

What is the lifetime of coroutineScope in Kotlin?

The Code A is from the project architecture samples at https://github.com/android/architecture-samples

1: I don't know if the function activateTask(task: Task) need to be wrapped with runBlocking just like Code B. I'm afraid that activateTask(task: Task) maybe not be run if the object of DefaultTasksRepository is destroyed quickly.

2: Normally I run coroutines in ViewModel.viewModelScope, I don't know whether the ViewModel.viewModelScope will be destroyed when I finish the app, and whether the coroutines running in ViewModel.viewModelScope will be destroyed too. If so, I think it will be bad, some long time coroutines such as writing data to remote server will be cancel.

3: And more, the function activateTask in Code A is a coroutines function, it can invoke another coroutines function directly, so I think the Code A+ is correct, right?

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 activateTask(task: Task) = withContext<Unit>(ioDispatcher) {
        coroutineScope {
            launch { tasksRemoteDataSource.activateTask(task) }
            launch { tasksLocalDataSource.activateTask(task) }
        }
    }

   override suspend fun clearCompletedTasks() {
        coroutineScope {
            launch { tasksRemoteDataSource.clearCompletedTasks() }
            launch { tasksLocalDataSource.clearCompletedTasks() }
        }
    }
   ...
}

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 activateTask(task: Task) = withContext<Unit>(ioDispatcher) {
        tasksRemoteDataSource.activateTask(task) 
        tasksLocalDataSource.activateTask(task)         
    }

   override suspend fun clearCompletedTasks() {            
        tasksRemoteDataSource.clearCompletedTasks() 
        tasksLocalDataSource.clearCompletedTasks()             
   }
   ...
}

Code B

fun main() = runBlocking { 
    launch { 
      delay(1000L)
      println("World!")
    }
    println("Hello,")
}

Upvotes: 1

Views: 1021

Answers (1)

Animesh Sahu
Animesh Sahu

Reputation: 8106

  1. You should not use runBlocking in any coroutine application, it blocks the thread.

    If you really want to make activateTask non-cancellable there is a factory implementation of NonCancellable already in the stdlib

    And you should not use coroutineScope wrapper inside the withContext, as a newly created CoroutineScope along with a new job is already passed as receiver within withContext.

    Implement your activateTask like this:

override suspend fun activateTask(task: Task) = withContext<Unit>(ioDispatcher + NonCancellable) {
    launch { tasksRemoteDataSource.activateTask(task) }
    launch { tasksLocalDataSource.activateTask(task) }
}

In this way it will be called on the IODispatcher but will not be cancellable since the Job element of the resulting context does not provide functionality to cancel it.

  1. ViewModelScope runs till your application is destroyed, more info and lifecycle chart is here. If you want to run some very important tasks, then use other dispatchers.

  2. Yes code A+ is completely correct

PS: You should not implement runBlocking in a coroutine application, its default implementation is just the event loop.

runBlocking is the way to bridge synchronous and asynchronous code

Better implementation of main function should be:

suspend fun main() = coroutineScope {
    // code here
}

It runs on the CommonPool, and if it suspends another coroutine could reuse the same thread.

Upvotes: 1

Related Questions