You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过协程实现循环等待任务完成后再启动下一轮周期执行?

问题:实现8小时内等待任务完成后再循环执行的协程逻辑

需要封装一个持续8小时的while循环,要求当前任务执行完毕后再启动下一轮,不能依赖固定延迟时间。

原实现代码

fun startPeriodicTask(timeout: Long, block: suspend CoroutineScope.() -> Unit) {
    periodicTask = viewModelScope.launcher {
        while (dataService.timeConfigData.runTime.value!!.toLong() < dataService.timeConfigData.timeEnd.value!!.toLong()) {
            withTimeout(timeout) {
                // 执行任务
                XLog.v("startPeriodicTask run block")
                block()
            }
            delay(timeout)
        }
    }
}

原调用代码

vm.startPeriodicTask(2000) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        mVibrator?.vibrate(VibrationEffect.createOneShot(1000, 255))
    } else {
        mVibrator?.vibrate(longArrayOf(1000, 2000), 1)
    }
}

原方案存在问题:协程的挂起机制没被正确利用,既无法实现等待任务完成的效果,还可能阻塞主线程。


修改后的代码及新问题

按方法修改后,未实现等待逻辑,while循环短时间内疯狂重复执行,任务被频繁触发:

fun startPeriodicTask(timeout: Long, block:() -> Unit) {
//        periodicTask = viewModelScope.launcher {
    while (dataService.timeConfigData.runTime.value!!.toLong() < 
    dataService.timeConfigData.timeEnd.value!!.toLong()) {
        XLog.v("startPeriodicTask run block")
        block()
//                delay(timeout)
//            }
    }
}


vm.dataService.testConfigData.isSpeakerSwitch.observe(viewLifecycleOwner) {
    if (it) {
        vm.startPeriodicTask(25000) {
            play {
                XLog.d("play end")
            }
        }
    }
}

fun play(onCompleted: () -> Unit) {
    stop()
    mMediaPlayer = MediaPlayer.create(requireContext(), R.raw.testsignal)
    mMediaPlayer?.setOnCompletionListener(OnCompletionListener {
        stop()
        onCompleted()
    })
    mMediaPlayer?.start()
}

循环短时间内重复执行的日志截图


解决方案

核心是将基于回调的异步任务转为挂起函数,配合协程实现"任务完成后再循环"的逻辑:

1. 把play转为挂起函数

利用suspendCancellableCoroutine将回调逻辑包装成挂起函数,让协程等待播放完成:

suspend fun play() {
    stop()
    return suspendCancellableCoroutine { cont ->
        mMediaPlayer = MediaPlayer.create(requireContext(), R.raw.testsignal)
        // 播放完成时恢复协程
        mMediaPlayer?.setOnCompletionListener {
            stop()
            cont.resume(Unit)
        }
        // 播放出错时抛出异常
        mMediaPlayer?.setOnErrorListener { _, _, _ ->
            stop()
            cont.resumeWithException(RuntimeException("MediaPlayer播放失败"))
            true
        }
        mMediaPlayer?.start()
        
        // 协程取消时释放资源
        cont.invokeOnCancellation {
            stop()
        }
    }
}

2. 修复循环逻辑

将循环放在协程内执行,确保任务完成后再进入下一轮:

fun startPeriodicTask() {
    periodicTask?.cancel() // 先取消旧任务,避免重复运行
    periodicTask = viewModelScope.launch {
        // 用空安全替代强制非空,避免空指针
        val runTime = dataService.timeConfigData.runTime.value?.toLong() ?: 0
        val timeEnd = dataService.timeConfigData.timeEnd.value?.toLong() ?: 0
        while (runTime < timeEnd) {
            XLog.v("startPeriodicTask run block")
            try {
                play() // 等待播放完成后才会继续循环
            } catch (e: Exception) {
                XLog.e("任务执行失败", e)
            }
            // 实时更新时间判断条件,避免循环用初始值一直执行
            val currentRunTime = dataService.timeConfigData.runTime.value?.toLong() ?: 0
            if (currentRunTime >= timeEnd) break
        }
    }
}

3. 调整调用逻辑

vm.dataService.testConfigData.isSpeakerSwitch.observe(viewLifecycleOwner) { isOn ->
    if (isOn) {
        vm.startPeriodicTask()
    } else {
        vm.periodicTask?.cancel() // 关闭时取消循环任务
    }
}

关键说明

  • 回调转挂起函数是协程处理异步等待的标准方式,确保任务完成前协程处于挂起状态,不会占用线程资源。
  • 循环放在协程内执行,完全非阻塞,不会影响主线程。
  • 增加空安全和错误处理,避免程序崩溃。

内容的提问来源于stack exchange,提问作者乐于助人的王老师

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 11:08:17