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

如何让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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 13:51:18