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

Kotlin对象销毁后如何避免协程资源泄漏及相关问题

问题:Kotlin协程对象销毁后仍运行导致资源泄漏

我在使用Kotlin协程时遇到问题:启动协程的对象被设为null后,协程仍持续运行,引发资源泄漏——对象已标记“销毁”,但协程仍执行并占用内存。

复现代码

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

class SomeClass {
    private val scope = CoroutineScope(Dispatchers.Default + CoroutineName("MyScope"))
    
    init {
        scope.launch {
            while (isActive) {
                delay(1000)  // Task runs every second
                val coroutineName = coroutineContext[CoroutineName]?.name ?: "Unknown"
                println("Executing periodic task in coroutine: $coroutineName")
            }
        }
    }
}

class SomeClass2 {
    private val scope = CoroutineScope(Dispatchers.Default)
    
    val sharedFlow = flow {
        while (coroutineContext.isActive) {
            delay(1000)  // Task runs every second
            val coroutineName = coroutineContext[CoroutineName]?.name ?: "Unknown"
            println("Executing periodic task in coroutine: $coroutineName")
            emit("Executing periodic task...")
        }
    }.shareIn(scope + CoroutineName("MyScope"), started = SharingStarted.Eagerly, replay = 1)
}

fun main() {
    var someClass: SomeClass2? = SomeClass2()

    // Simulate the object working for a while
    Thread.sleep(3000)

    // Simulate the "death" of the object
    someClass = null

    // Program continues running
    Thread.sleep(5000)

    // Try to trigger garbage collection
    val dataList = mutableListOf<DataClass>()
    for (i in 1..100000000) {
        dataList.add(DataClass("OLOLOOLOLOLOLOLOLOLOLOO$i"))
    }

    println("Program continues running")
    println(dataList.size)

    // Program continues running
    Thread.sleep(5000)

    println("End of main")
}

data class DataClass(val a: String)

输出结果

Executing periodic task in coroutine: MyScope
Executing periodic task in coroutine: MyScope
Executing periodic task in coroutine: MyScope
Executing periodic task in coroutine: MyScope
Executing periodic task in coroutine: MyScope
Executing periodic task in coroutine: MyScope
Executing periodic task in coroutine: MyScope
Executing periodic task in coroutine: MyScope
Executing periodic task in coroutine: MyScope
Executing periodic task in coroutine: MyScope
Program continues running
100000000
Executing periodic task in coroutine: MyScope
Executing periodic task in coroutine: MyScope
Executing periodic task in coroutine: MyScope
Executing periodic task in coroutine: MyScope
Executing periodic task in coroutine: MyScope
End of main

核心问题

如何正确处理协程取消避免资源泄漏?Kotlin是否有推荐模式确保持有协程的对象不再被使用时,协程能终止?


更新补充问题

感谢此前解答!我已了解协程取消是类或类使用者的责任,但针对CoroutineScope通过Dagger注入或在sharedFlow中使用的场景,还有两个问题:

  1. 当CoroutineScope通过Dagger 2注入时,如何正确取消协程?Dagger创建并提供对象但不处理销毁,若CoroutineScope通过Dagger注入,应如何管理该Scope下协程的生命周期,确保关联对象不再被需要时协程能正确取消?
  2. 是否需要手动停止sharedFlow?示例中使用了内部启动协程的sharedFlow,是否需要手动停止该Flow避免资源泄漏?如果需要,最佳实践是什么?

解决方案

核心问题:协程取消的正确姿势

Kotlin协程的取消是协作式的,将持有CoroutineScope的对象置为null不会自动取消协程——因为协程会引用Scope,导致对象无法被GC回收,协程持续运行。

推荐模式:绑定Scope与类生命周期

让持有协程的类实现CoroutineScope接口,手动管理Scope的取消:

class SomeClass : CoroutineScope {
    // 用SupervisorJob确保取消Scope时递归终止所有子协程
    override val coroutineContext = Dispatchers.Default + CoroutineName("MyScope") + SupervisorJob()
    
    init {
        launch {
            while (isActive) {
                delay(1000)
                val coroutineName = coroutineContext[CoroutineName]?.name ?: "Unknown"
                println("Executing periodic task in coroutine: $coroutineName")
            }
        }
    }

    // 对象销毁前调用此方法终止所有协程
    fun destroy() {
        coroutineContext.cancel()
    }
}

使用时先取消协程再置空对象:

fun main() {
    var someClass: SomeClass? = SomeClass()
    Thread.sleep(3000)
    someClass?.destroy()
    someClass = null
    // 后续逻辑...
}

补充问题解答

1. Dagger注入CoroutineScope的协程管理

Dagger不管理对象销毁,需结合以下方案处理:

  • Android场景:绑定生命周期
    为Activity/Fragment注入lifecycleScope或ViewModelScope,这些Scope会随组件生命周期自动取消协程;自定义类可实现LifecycleObserver,在onDestroy回调中调用Scope的cancel()。
  • 通用场景:手动关闭Scope
    封装可关闭的Scope类,在对象不再使用时手动调用关闭:
    class CloseableCoroutineScope(
        override val coroutineContext: CoroutineContext = Dispatchers.Default + SupervisorJob()
    ) : CoroutineScope, Closeable {
        override fun close() {
            coroutineContext.cancel()
        }
    }
    
    在Dagger模块中提供该类,使用方在对象销毁时调用close()方法。

2. SharedFlow的资源泄漏处理

是否需要手动停止取决于started参数:

  • 示例中使用SharingStarted.Eagerly会让SharedFlow持续运行,即使无订阅者,必须手动取消其所在Scope来终止协程。
  • 最佳实践:自动随订阅启停
    使用SharingStarted.WhileSubscribed(),无订阅者时自动停止协程,有新订阅时重启:
    val sharedFlow = flow {
        while (coroutineContext.isActive) {
            delay(1000)
            val coroutineName = coroutineContext[CoroutineName]?.name ?: "Unknown"
            println("Executing periodic task in coroutine: $coroutineName")
            emit("Executing periodic task...")
        }
    }.shareIn(
        scope + CoroutineName("MyScope"),
        started = SharingStarted.WhileSubscribed(stopTimeoutMillis = 5000), // 无订阅5秒后停止
        replay = 1
    )
    

内容的提问来源于stack exchange,提问作者Db Mongo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 08:42:41