如何使用Kotlin中的协程按顺序执行多个函数

huangapple go评论52阅读模式
英文:

How to execute multiple functions in order with Coroutine in Kotlin

问题

以下是您要翻译的内容:

步骤1函数

suspend fun userIdToArrayFun() = withContext(Dispatchers.IO) {
    userDB.get()
        .addOnSuccessListener { documents ->
            for (document in documents) {
                var userId = document.data?.getValue("userId").toString()
                var userEntry = UserPoint(
                    userId,
                    0,
                    0
                )

                userPointArray.add(userEntry)
                dataToUserPointArray.userIdToArray.value = true
            }
        }
    true
}

步骤2函数

suspend fun calculatePointFun() = withContext(Dispatchers.IO) {
    try {
        listOf(
            launch { stepCountToArrayFun() },
            launch { diaryToArrayFun() },
            launch { commentToArrayFun() },
            launch { likeToArrayFun() }
        ).joinAll()
        false
    } catch (e: Throwable) {
        true
    }
}

步骤3函数

fun indexArrayFun() = with(Dispatchers.IO) {
    userPointArray.sortWith(compareBy { it.point })

    var initialindex: Int = 1
    for (i in userPointArray.indices) {
        userPointArray[i].point = initialindex

        if (userPointArray[i + 1].point != userPointArray[i].point) {
            initialindex++
        }
    }
    true
}

最后,我以以下方式按顺序执行这些步骤:

var fullUserArrayPointFun = CoroutineScope(Dispatchers.Default).launch {
        
        if (userIdToArrayFun()) {
            calculatePointFun()
        }
        if(calculatePointFun()) {
            indexArrayFun()
        }
    }

请注意,我已将HTML实体代码(如"<"和">")替换为正常的字符,以便更容易理解。如果您需要更多帮助或有其他问题,请告诉我。

英文:

I have six functions and in large, I will execute them in three steps.

First, variable userPointArray that will be used for it looks like below.

var userPointArray = ArrayList&lt;UserPoint&gt;()

data class UserPoint(
    var userId: String,
    var point: Int,
    var index: Int
)

My three steps will be below.

  1. get userId data from firebase and put it to userPointArray
    -> one function
  2. get four data from firebase and calculate point field -> four functions
  3. update index based on point -> one function

Since I need to execute total 6 functions in three steps order, I will use Coroutine.

step 1 function

suspend fun userIdToArrayFun() = withContext(Dispatchers.IO) {
        userDB.get()
            .addOnSuccessListener { documents -&gt;
                for (document in documents) {
                    var userId = document.data?.getValue(&quot;userId&quot;).toString()
                    var userEntry = UserPoint(
                        userId,
                        0,
                        0
                    )

                    userPointArray.add(userEntry)
                    dataToUserPointArray.userIdToArray.value = true
                }
            }
        true
    }

step 2 functions

: Four functions in this step are stepCountToArrayFun(), diaryArrayFun() ...

suspend fun calculatePointFun() = withContext(Dispatchers.IO) {
        try {
            listOf(
                launch { stepCountToArrayFun() },
                launch { diaryToArrayFun() },
                launch { commentToArrayFun() },
                launch { likeToArrayFun() }
            ).joinAll()
            false
        } catch (e: Throwable) {
            true
        }
    }

step3 Function

 fun indexArrayFun() = with(Dispatchers.IO) {
        userPointArray.sortWith(compareBy { it.point })

        var initialindex: Int = 1
        for (i in userPointArray.indices) {
            userPointArray[i].point = initialindex

            if (userPointArray[i + 1].point != userPointArray[i].point) {
                initialindex++
            }
        }
        true
    }

And lastly, I execute these order step 1 -> step2 -> step 3 as below:

var fullUserArrayPointFun = CoroutineScope(Dispatchers.Default).launch {
        
        if (userIdToArrayFun()) {
            calculatePointFun()
        }
        if(calculatePointFun()) {
            indexArrayFun()
        }
    }

Since I'm new in Coroutine, I just write code as searching, I guess my code is weird, especially my last code that executes these three step code in order.

Can you review each three step code and my last code?

答案1

得分: 0

我建议先阅读Roman Elizarov在Medium上的文档和博客文章,以在尝试使用协程之前牢固掌握基本的协程概念。

以下是您发布的代码的简要回顾:

步骤1函数

使用挂起函数来调用异步函数没有意义。您的挂起函数不会等待它。您可以使用suspendCancellableCoroutine将异步函数转换为挂起函数,但对于Firebase来说这是不必要的,因为Firebase已经为您提供了一个挂起函数。您应该使用await()挂起函数,而不是添加一个异步监听器。代码应该类似于以下内容,但我可能有点不准确,因为我不使用Firebase,也不太了解您在for循环中的具体操作:

suspend fun userIdToArrayFun() {
    try {
        val documents = userDB.get().await()
        for (document in documents) {
            var userId = document.data?.getValue("userId").toString()
            var userEntry = UserPoint(
                userId,
                0,
                0
            )

            userPointArray.add(userEntry)
            dataToUserPointArray.userIdToArray.value = true
        }
    } catch (e: Exception) {
        // 记录错误?向用户显示错误?
    }
}

步骤2函数

如果您调用的四个函数是阻塞函数,那么您当前的代码是正确的。如果它们是挂起函数或非阻塞函数,那么切换上下文没有意义,可以为了代码清晰性而使用coroutineScope {而不是withContext(Dispatchers.IO) {

如果这四个函数旨在成为等待结果的挂起函数,那么我对步骤1的评论也适用于您的实现。

步骤3函数

您使用with(Dispatchers.IO)IO作为lambda的接收者,这是with作用域函数。我认为您混淆了它与withContext

如果这段代码不在处理大型数组,那么它不会阻塞,因此您可以删除with包装,保持原样。但如果数据量足够大,需要一定的时间,您可以将其更改为挂起函数,并使用withContext(Dispatchers.Default)将工作从协程所在的线程卸载。建议使用Default而不是IO,因为它不受IO限制。

最后一步

不应该创建一个CoroutineScope()而不将其存储为属性以管理其生命周期。如果您真的不需要管理其生命周期,可以使用GlobalScope,但不关心协程的生命周期应该非常罕见。它会占用资源,因此当关联的UI变得过时时,您希望取消协程,以防资源浪费(内存泄漏和/或不必要的CPU/网络/磁盘使用)。

在Android上,绝大多数协程应该从lifecycleScopeviewModelScope启动,因为这些CoroutineScope已经为您提供,而且在关联的UI销毁时会自动取消。

此外,您两次调用了calculatePointFun(),这没有意义。我建议将您的代码编写如下。除非您有需要提前取消它的特殊情况,否则不需要将协程的Job分配给一个变量。

您将userIdToArrayFun()用作如果成功返回true,失败返回false的方式。是否有意这样设计?

val fullUserArrayPointJob = viewModelScope.launch {
    if (userIdToArrayFun()) {
        if (calculatePointFun()) {
            indexArrayFun()
        }
    }
}

// 或

val fullUserArrayPointJob = viewModelScope.launch {
    if (!userIdToArrayFun()) {
        return@launch
    }
    if (calculatePointFun()) {
        indexArrayFun()
    }
}
英文:

I recommend reading through the documentation and blog posts on Medium by Roman Elizarov to get a firm grasp of the basic coroutine concepts before attempting to use them.

Here's a brief review of the code you posted:

Step 1 function

It doesn't make sense to use a suspend function to call an asynchronous function. Your suspend function is not going to wait for it. You can convert asynchronous functions into suspend functions by using suspendCancellableCoroutine, but it's unnecessary for Firebase, since it already provides a suspend function for you. You should use the await() suspend function instead of adding an asynchronous listener. It should look something like this, but I might be a little bit off because I don't use Firebase and I don't really know what you're doing in your for loop:

suspend fun userIdToArrayFun() {
    try {
        val documents = userDB.get().await()
        for (document in documents) {
            var userId = document.data?.getValue(&quot;userId&quot;).toString()
            var userEntry = UserPoint(
                userId,
                0,
                0
            )

            userPointArray.add(userEntry)
            dataToUserPointArray.userIdToArray.value = true
        }
    } catch (e: Exception) {
        // log error? Show error to user?
    }
}

Step 2 functions

If your four functions that you are calling are blocking functions, your current code is correct. If they are suspend functions or non-blocking, then there's no point in switching contexts, and can use coroutineScope { instead of withContext(Dispatchers.IO) { for code clarity.

If the four functions are intended to be suspend function that wait for results, then my comments on step 1 also apply to how you have implemented them.

Step 3 functions

Your use of with(Dispatchers.IO) is making IO the receiver of the lambda--it's the with scope function. I think you mixed it up with withContext.

If this code is not working with an enormous array, it's not really blocking, so you can just remove the with wrapper and leave it as is. But if it is enough data to take a certain amount of time, you can change it to a suspend function and use withContext(Dispatchers.Default) to offload the work from whatever thread is working on the coroutine. I suggest Default instead of IO since it's not IO-bound.

Last step

You shouldn't be creating a CoroutineScope() without storing it in a property so you can manage its lifecycle. If you truly don't have to manage its lifecycle, you can use GlobalScope, but it should be very rare for you not to care about a coroutine's lifecycle. It's using resources, and so when whatever UI it is related to becomes obsolete, you want to cancel the coroutines so the resources aren't wasted (memory leak and/or unnecessary CPU/network/disk use).

On Android, the vast majority of coroutines should be launched either from a lifecycleScope or viewModelScope because these CoroutineScopes are already available for you and already automatically cancel themselves when their associated UI is destroyed.

Also, you are calling your calculatePointFun() twice, which doesn't make sense. I would write your code like below. You don't need to assign your coroutine's Job to a variable, unless you have some circumstance under which you need to cancel it early.

You are using userIdToArrayFun() as if it returns a Boolean. Did you mean to make it return false when it fails and true for success?

val fullUserArrayPointJob = viewModelScope.launch {
    if (userIdToArrayFun()) {
        if(calculatePointFun()) {
            indexArrayFun()
        }
    }
}

// or

val fullUserArrayPointJob = viewModelScope.launch {
    if (!userIdToArrayFun()) {
        return@launch
    }
    if(calculatePointFun()) {
        indexArrayFun()
    }
}

</details>



huangapple
  • 本文由 发表于 2023年2月24日 02:38:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/75548998.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定