在Kotlin中使用协程时,如何保证获取最新数据?

时间:2020-04-22 00:55:30

标签: kotlin kotlin-coroutines

代码A来自项目体系结构样本,您可以here看到它。

updateTasksFromRemoteDataSource()是暂停函数,因此它可能异步运行。

当我使用参数getTasks(forceUpdate: Boolean)调用函数True时,恐怕return tasksLocalDataSource.getTasks()会在updateTasksFromRemoteDataSource()之前被触发。

我不知道代码B是否可以保证return tasksLocalDataSource.getTasks()之后会触发updateTasksFromRemoteDataSource()

代码A

class DefaultTasksRepository(
    private val tasksRemoteDataSource: TasksDataSource,
    private val tasksLocalDataSource: TasksDataSource,
    private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) : TasksRepository {

   override suspend fun getTasks(forceUpdate: Boolean): Result<List<Task>> {
        // Set app as busy while this function executes.
        wrapEspressoIdlingResource {

            if (forceUpdate) {
                try {
                    updateTasksFromRemoteDataSource()
                } catch (ex: Exception) {
                    return Result.Error(ex)
                }
            }
            return tasksLocalDataSource.getTasks()
        }
   }

   private suspend fun updateTasksFromRemoteDataSource() {
        val remoteTasks = tasksRemoteDataSource.getTasks()

        if (remoteTasks is Success) {
            // Real apps might want to do a proper sync, deleting, modifying or adding each task.
            tasksLocalDataSource.deleteAllTasks()
            remoteTasks.data.forEach { task ->
                tasksLocalDataSource.saveTask(task)
            }
        } else if (remoteTasks is Result.Error) {
            throw remoteTasks.exception
        }
    }
   ...

}

代码B

class DefaultTasksRepository(
    private val tasksRemoteDataSource: TasksDataSource,
    private val tasksLocalDataSource: TasksDataSource,
    private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) : TasksRepository {

    override suspend fun getTasks(forceUpdate: Boolean): Result<List<Task>> {
        // Set app as busy while this function executes.
        wrapEspressoIdlingResource {
            coroutineScope {
                if (forceUpdate) {
                    try {
                        updateTasksFromRemoteDataSource()
                    } catch (ex: Exception) {
                        return Result.Error(ex)
                    }
                }
            }
            return tasksLocalDataSource.getTasks()
        }
    }

    ...    

}

添加的内容

致Tenfour04:谢谢!

如果有人像代码C一样用updateTasksFromRemoteDataSource()实现lauch,那么您确定在我调用函数{{时,会在return tasksLocalDataSource.getTasks()之后触发代码C是updateTasksFromRemoteDataSource() 1}}和参数getTasks(forceUpdate: Boolean)

代码C

True

新增内容

致乔弗里:谢谢!

我认为代码D可以编译。

在这种情况下,当 class DefaultTasksRepository( private val tasksRemoteDataSource: TasksDataSource, private val tasksLocalDataSource: TasksDataSource, private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO ) : TasksRepository { override suspend fun getTasks(forceUpdate: Boolean): Result<List<Task>> { // Set app as busy while this function executes. wrapEspressoIdlingResource { if (forceUpdate) { try { updateTasksFromRemoteDataSource() } catch (ex: Exception) { return Result.Error(ex) } } return tasksLocalDataSource.getTasks() } } private suspend fun updateTasksFromRemoteDataSource() { val remoteTasks = tasksRemoteDataSource.getTasks() if (remoteTasks is Success) { // Real apps might want to do a proper sync, deleting, modifying or adding each task. tasksLocalDataSource.deleteAllTasks() launch { //I suppose that launch can be fired remoteTasks.data.forEach { task -> tasksLocalDataSource.saveTask(task) } } } else if (remoteTasks is Result.Error) { throw remoteTasks.exception } } } 为真时,forceUpdate可能会在tasksLocalDataSource.getTasks()完成之前运行。

代码D

updateTasksFromRemoteDataSource()

1 个答案:

答案 0 :(得分:4)

从呼叫站点的角度来看,

suspend函数看起来像常规函数,因为它们像常规同步函数一样顺序执行。 我的意思是,在普通调用suspend函数之后执行的指令要等到被调用函数完成执行后才能执行。

这意味着代码A很好(当forceUpdate为真,tasksLocalDataSource.getTasks()永远不会在updateTasksFromRemoteDataSource()完成之前运行),并且代码B中的coroutineScope是不必要的

关于代码C,结构化并发可以节省您的时间。 没有launch接收者,人们根本无法拨打CoroutineScope。 由于TaskRepository不扩展CoroutineScope,因此按原样编译代码C。

尽管有两种方法可以进行编译:

  1. 使用GlobalScope.launch {}:的确会引起您期望的问题。这样的launch的主体将异步运行,并且与调用方无关。在这种情况下,updateTasksFromRemoteDataSource可以在launch的主体完成之前返回。控制此操作的唯一方法是在对.join()的调用返回的Job上使用launch(等待完成)。这就是为什么通常不建议使用GlobalScope的原因,因为它可以“泄漏”协程。

  2. launch内的coroutineScope {...}中包装对updateTasksFromRemoteDataSource的调用。这将确保在coroutineScope调用完成之前,在coroutineScope块内启动的所有协程实际上已经完成。请注意,coroutineScope块内 的所有内容都可以很好地并发运行,这取决于如何使用launch / async,但这是重点首先使用launch是不是?

现在有了代码D,我对代码C的回答仍然成立。无论您是传递作用域还是使用GlobalScope,都将有效地创建比启动它们的暂停功能更长的生命周期的协程。 因此,它确实会产生您担心的问题。 但是,如果您不希望实现者在提供的范围内启动长期存在的协程,您为什么还要通过CoroutineScope

假设您不这样做,那么开发人员不太可能会使用GlobalScope(或任何范围)来执行此操作。从悬挂函数创建长寿的协程通常是不好的风格。如果您的函数正在挂起,则调用者通常希望它在完成时实际上已经完成了工作。