如何让Kotlin协程永久运行?Poller后台常驻问题
问题描述
我有一个Poller类,用于通过轮询操作从API获取数据(所有操作均借助协程实现异步)。示例类如下:
class Poller() { private var isRunning = false private var job: Job? = null init { isRunning = true job = GlobalScope.launch { while (isRunning) { launch { updater() } delay(10_000) } } } // ... }
我的main函数:
fun main() = runBlocking { val poller = Poller() // do other stuff or nothings }
主线程执行完val poller = Poller()后就退出并终止了应用程序。如何让Poller在后台持续运行(以便我可以执行其他操作或空转)?
编辑:
我尝试将GlobalScope.launch { ... }替换为:
val threadPool = Executors.newFixedThreadPool(4) val dispatcher = threadPool.asCoroutineDispatcher() job = CoroutineScope(dispatcher).launch { while (isRunning) { launch { clanUpdater() } delay(10_000) } }
但应用程序仍然会停止。
解决方案
问题根源在于runBlocking会在其内部协程体执行完毕后立即结束,而Poller中启动的协程属于独立的CoroutineScope(不管是GlobalScope还是你自定义的Scope),runBlocking不会主动等待这些外部协程完成,因此主线程直接退出,整个程序终止。
方法1:让runBlocking等待Poller的协程完成
给Poller添加一个方法,用于等待其内部轮询协程结束:
class Poller { private var isRunning = true private var job: Job? = null init { job = GlobalScope.launch { while (isRunning) { launch { updater() } delay(10_000) } } } // 添加这个方法,让外部可以等待轮询协程结束 suspend fun waitUntilStopped() { job?.join() } // 可选:添加停止轮询的方法 fun stop() { isRunning = false } // ... 其他方法 }
然后修改main函数,调用这个等待方法:
fun main() = runBlocking { val poller = Poller() // 在这里执行其他操作,比如: // launch { doOtherAsyncWork() } // 等待Poller的轮询协程结束,否则runBlocking会直接退出 poller.waitUntilStopped() }
方法2:优化Poller的Scope管理(推荐)
不推荐使用GlobalScope,更好的做法是让Poller自己管理专属的CoroutineScope,同时保证isRunning的线程安全(用AtomicBoolean):
import kotlinx.coroutines.* import java.util.concurrent.atomic.AtomicBoolean class Poller { private val isRunning = AtomicBoolean(true) // 创建专属Scope,绑定SupervisorJob避免子协程异常影响整个Scope private val pollerScope = CoroutineScope(Dispatchers.Default + SupervisorJob()) private var job: Job? = null init { job = pollerScope.launch { while (isRunning.get()) { launch { updater() } delay(10_000) } } } suspend fun waitUntilStopped() { job?.join() } fun stop() { isRunning.set(false) // 可选:取消整个Scope下的所有协程 pollerScope.cancel() } // ... 其他方法 }
main函数的调用方式和方法1一致。这样做的好处是:
- 避免
GlobalScope带来的生命周期管理问题 AtomicBoolean保证isRunning的线程安全,防止并发修改导致的逻辑错误- 可以通过
pollerScope.cancel()一次性取消所有相关协程,资源清理更彻底
为什么你之前修改Dispatcher没用?
你更换了协程的Dispatcher,但本质上还是没有让runBlocking等待Poller的协程完成。runBlocking只关心自己内部协程体的执行进度,外部Scope的协程不会影响它的结束时机,所以程序还是会在runBlocking协程体执行完后立即退出。
内容的提问来源于stack exchange,提问作者Maicol
相关产品推荐
相关产品推荐

