为什么使用Job.cancel() 的Flow 启动两次后无法取消?

Why can't I cancel a Flow using Job.cancel() when I start it for two times?

在App中,我点击“开始”按钮每100ms显示一次新信息,然后点击“停止”按钮停止显示新信息。和我预期的一样。

但是如果我点击“开始”按钮两次,然后我点击“停止”按钮希望停止显示新信息,但是新信息一直显示,为什么?好像myJob.cancel()不行。

class HandleMeter: ViewModel() {

  var myInfo = mutableStateOf("Hello")

  private lateinit var myJob: Job

    private fun soundDbFlow(period: Long = 100) = flow {
        while (true) {
            emit(soundDb())
            delay(period)
        }
    }

    fun calCurrentAsynNew() {
        myJob = viewModelScope.launch(Dispatchers.IO) {
            soundDbFlow().collect { myInfo.value = it.toString() + " OK Asyn  " + a++.toString() }
        }
    }

    fun cancelJob(){
        myJob.cancel()
    }
    
    ...
}


@Composable
fun Greeting(handleMeter: HandleMeter) {

    var info = handleMeter.myInfo    
    Column(
        modifier = Modifier.fillMaxSize()
    ) {
        Text(text = "Hello ${info.value}")

        Button(
            onClick = { handleMeter.calCurrentAsynNew() }
        ) {
            Text("Start")
        }

        Button(
              onClick = { handleMeter.cancelJob() }
        ) {
            Text("Stop")
        }
    }
}

当您调用 handleMeter.calCurrentAsynNew() 时,它会启动一个新协程并将返回的 Job 分配给 myJob。当你第二次点击 Start 时,它会再次启动一个新的协程并将 myJob 的值更新为这个新的 Job 实例,但这并没有取消之前的协程,它仍然是 运行.

调用calCurrentAsynNew()时如果想取消之前的协程,需要使用myJob.cancel()

手动取消
fun calCurrentAsynNew() {
    if(::myJob.isInitialized)
        myJob.cancel()
    myJob = viewModelScope.launch {
        soundDbFlow().collect { myInfo.value = it.toString() + " OK Asyn  " + a++.toString() }
    }
}

除了使用 lateinit var myJob: Job 你也可以使用 var myJob: Job? = null 然后取消它,使用 myJob?.cancel()

在这种情况下也不需要

Dispatchers.IO