我可以定期使用 kotlin-coroutines 运行 任务吗?

Can I use kotlin-coroutines run a task periodically?

看来我可以使用代码A来定期运行一个任务。

你知道 soundDb() 可以每 100 毫秒触发一次,就像 运行 周期性触发一样。

定期使用 kotlin-coroutines 运行 任务是个好方法吗?

代码A

fun calCurrentAsyn() {
    viewModelScope.launch {
        var b = 0.0
        for (i in 1..5) {
            b = b + soundDb()
            delay(100)
        }
        b = b / 5.0
        myInfo.value = b.toString() + " OK Asyn  " + a++.toString()
    }
}

suspend fun soundDb(): Double {
    var k=0.0
    for (i in 1..500000000){
        k=k+i
    }
    return k
}

新增内容:

致乔佛里:谢谢!

1:我知道代码B是更好的风格,代码A和代码B执行的效果会一样吗?

代码B

viewModelScope.launch {
    val b = computeCurrent()
    myInfo.value = "$b OK Asyn  ${a++}"
}

suspend fun computeCurrent(): Double {
    var b = 0.0
    repeat(5) {
        b += soundDb()
        delay(100)
    }
    return b / 5.0
}

suspend fun soundDb(): Double {
    var k=0.0
    for (i in 1..500000000){
        k=k+i
    }
    return k
}

2:我希望从一个周期任务较长的运行ning协程中定时获取信息,如何取消流程soundDbFlow().runningAverage()

代码C

 viewModelScope.launch {
      soundDbFlow().runningAverage().collect {
        println("Average = $it") // do something with it
      }
   }

3:你知道我可以使用Timer().scheduleAtFixedRate在后台线程中定期获取信息,就像代码D一样,它介于Timer().scheduleAtFixedRateFlow之间?

代码D

   private fun startTimer() {
        timer = Timer()
        timer.scheduleAtFixedRate(timerTask {
            recordingTime = recordingTime + 1
            val temp = fromCountToTimeByInterval(recordingTime, 10)
            _timeElapse.postValue(temp) 
        }, 0, 10) 
    }

    private fun stopTimer() {
        timer.cancel()
        recordingTime = 0
        _timeElapse.postValue("00:00.00")
    }

重复任务的方法(launch + 循环)本身并不错,但问题在于您希望这个协程如何影响应用程序的其余部分。

很难判断这是为了问题而举的例子,还是你的实际代码。如果是您的实际代码,则您的用例不是经典的“周期性任务运行”:

  • 它有固定的迭代次数
  • 它只在执行结束时有副作用

这表明将此代码编写为挂起函数可能更有意义:

suspend fun computeCurrent(): Double {
    var b = 0.0
    repeat(5) {
        b += soundDb()
        delay(100)
    }
    return b / 5.0
}

然后像这样使用,更清楚结果用在什么地方:

viewModelScope.launch {
    val b = computeCurrent()
    myInfo.value = "$b OK Asyn  ${a++}"
}

也许您实际上不需要以异步方式启动该协程(这可能取决于您如何进行其他类似调用)。

如果您需要定期(而不仅仅是在最后)从具有周期性任务的长 运行ning 协程中获取信息,您可能需要考虑构建一个 Flow 并收集它应用 side-effects:

import kotlinx.coroutines.flow.*
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds

fun soundDbFlow(period: Duration = 100.milliseconds) = flow {
    while (true) {
        emit(soundDb())
        delay(period)
    }
}

fun Flow<Double>.runningAverage(): Flow<Double> = flow {
    var valuesCount = 0
    var sum = 0.0
    collect { value ->
        sum += value
        valuesCount++
        emit(sum / valuesCount)
    }
}

然后用法可能是这样的:

viewModelScope.launch {
    soundDbFlow().take(5).runningAverage().collect {
        println("Average = $it") // do something with it
    }
}

关于修改后的问题:

I know that Code B is the better style, will the effect of execution be same between Code A and Code B ?

代码 A 和代码 B 的行为相同。我的观点确实有一半是关于样式的,因为使它成为一个简单的挂起函数可以清楚地表明(对您和读者)它只是 returns 一个单一值。这似乎是您在新添加的 soundDb() 函数中也犯的一个错误,我不确定您是否清楚循环不是流,并且您只从这些函数返回一个值(不是多次更新任何内容)。

我的另一半观点是,因为它只是您更新的单个值,所以在 long-running 协同程序中它甚至可能不需要 运行。您可以在需要时将其与其他暂停代码集成。

how can I cancel the flow soundDbFlow().runningAverage() ?

如果取消收集协程(通过您 launch 编辑的作业或通过取消整个 viewModelScope - 当不需要组件时自动发生),流程会自动取消。如果您使用提前结束收集的终端运算符,例如 first()takeWhile()take(n).collect { .. } 等,流程也会被取消。

You know I can use Timer().scheduleAtFixedRate to get information regularly in background thread just like Code D, which is the between Timer().scheduleAtFixedRate and Flow ?

老实说,由你决定。如果您已经在使用协程,我个人更喜欢流程方法。 scheduleAtFixedRate 不会与结构化并发集成,需要手动管理取消。