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

如何防止Kotlin协程重复调用?实现同标签协程仅运行一个

Kotlin协程实现同标签互斥运行的方案

Kotlin协程本身没有原生支持“标签+同标签互斥”的API,但可以通过自定义扩展函数结合状态管理实现你要的效果。根据你期望的“同一时刻仅运行一个同标签协程,后续同标签协程直接忽略”的需求,以下是具体实现:

方案1:忽略后续同标签协程(匹配你的示例输出)

维护一个映射表记录每个标签当前活跃的协程Job,新的同标签协程启动时,先检查是否已有活跃的同标签协程,有则直接跳过启动:

import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock

// 存储标签与对应活跃Job的映射,需保证线程安全
private val activeTagJobs = mutableMapOf<String, Job>()
private val mapMutex = Mutex() // 保护映射表的线程安全操作

// 给CoroutineScope扩展自定义launch函数
fun CoroutineScope.launchSingleWithTag(
    context: CoroutineContext = EmptyCoroutineContext,
    tag: String,
    block: suspend CoroutineScope.() -> Unit
): Job? {
    return mapMutex.withLock {
        // 检查当前标签是否有活跃协程
        val existingActiveJob = activeTagJobs[tag]
        if (existingActiveJob?.isActive == true) {
            // 已有活跃协程,返回null表示不启动新协程
            return@withLock null
        }

        // 启动新协程并记录到映射表
        val newJob = launch(context) {
            try {
                block()
            } finally {
                // 协程结束后移除标签记录
                mapMutex.withLock {
                    activeTagJobs.remove(tag)
                }
            }
        }
        activeTagJobs[tag] = newJob
        newJob
    }
}

使用示例

// 建议用自定义Scope替代GlobalScope,避免内存泄漏
val appScope = CoroutineScope(Dispatchers.IO + SupervisorJob())

appScope.launchSingleWithTag(Dispatchers.IO, "tag_a") {
    println("Hello World ${System.currentTimeMillis()}")
    delay(1000) // 模拟耗时任务
}

// 此时第一个协程仍在运行,第二个同标签协程会被忽略
appScope.launchSingleWithTag(Dispatchers.IO, "tag_a") {
    println("Hello World ${System.currentTimeMillis()}")
}

// 输出结果仅一行:
// Hello World 1718XXXXXXX

方案2:同标签协程串行执行

如果希望后续同标签协程不被忽略,而是等前面的执行完再运行,可以用Mutex实现串行化:

private val tagMutexMap = mutableMapOf<String, Mutex>()
private val mutexMapLock = Mutex()

fun CoroutineScope.launchSerialWithTag(
    context: CoroutineContext = EmptyCoroutineContext,
    tag: String,
    block: suspend CoroutineScope.() -> Unit
): Job {
    return launch(context) {
        // 获取或创建对应标签的Mutex
        val mutex = mutexMapLock.withLock {
            tagMutexMap.getOrPut(tag) { Mutex() }
        }
        // 加锁后执行协程体,确保同一时间仅一个同标签协程运行
        mutex.withLock {
            block()
        }
    }
}

注意事项

  • 避免使用GlobalScope,它的协程不受生命周期管理,易引发内存泄漏,建议使用和业务生命周期绑定的自定义CoroutineScope。
  • 两种方案按需选择:需丢弃重复请求选方案1,需排队执行选方案2。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 19:54:41