You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Kotlin Deferred的anyOf等价实现:select用法是否可行及开销如何?

Great question! Let's break down how to wait for the first completed Deferred in Kotlin coroutines, including whether select works, its performance, and alternative approaches.

Kotlin Coroutines: Waiting for Any Deferred to Complete (Equivalent to CompletableFuture.anyOf)

Short Answer

Yes, you can use the select expression to achieve exactly what Java's CompletableFuture.anyOf does—it's the idiomatic Kotlin coroutine approach for this use case. There's also a convenience function firstCompletedOf that wraps this logic if you don't need select's flexibility.

Using select to Wait for the First Completion

Your initial idea is on the right track, but let's adjust it to actually capture the result of the first finished Deferred. The select expression suspends the coroutine until one of the onAwait clauses triggers, then returns that result immediately.

Here's a concrete, runnable example:

import kotlinx.coroutines.*
import kotlinx.coroutines.selects.select

fun main() = runBlocking {
    val deferred1 = async(Dispatchers.IO) {
        delay(2000)
        "Result from Deferred 1"
    }
    
    val deferred2 = async(Dispatchers.IO) {
        delay(1000)
        "Result from Deferred 2"
    }

    val firstResult = select<String> {
        deferred1.onAwait { it }
        deferred2.onAwait { it }
    }

    println("First completed result: $firstResult") // Prints "Result from Deferred 2"
    
    // Optional but recommended: Cancel unused deferreds to avoid wasted work
    deferred1.cancel()
    deferred2.cancel()
}

Key Details About This Approach:

  • select suspends until the first Deferred completes (successfully or with an exception). If one fails, the exception is rethrown immediately.
  • You can handle exceptions explicitly by wrapping onAwait blocks in try/catch if you want to skip failed deferreds.
  • Cancel remaining deferreds after getting your result—this frees up resources since they don't need to finish their work.

The Convenience Alternative: firstCompletedOf

If you don't need select's flexibility (like adding non-onAwait clauses), use the firstCompletedOf function from the kotlinx.coroutines library. It's a shorthand for the select pattern above:

import kotlinx.coroutines.*
import kotlinx.coroutines.firstCompletedOf

fun main() = runBlocking {
    val deferreds = listOf(
        async(Dispatchers.IO) { delay(2000); "Result 1" },
        async(Dispatchers.IO) { delay(1000); "Result 2" }
    )

    val firstResult = firstCompletedOf(deferreds)
    println("First completed result: $firstResult")
    
    deferreds.forEach { it.cancel() }
}

Performance Overhead of select

Great call asking about performance—here's the good news: select is extremely lightweight, with negligible overhead compared to waiting for a single Deferred.

Why?

  • Unlike CompletableFuture.anyOf, which relies on thread-based listeners, select integrates directly with the coroutine dispatcher's scheduling queue. No extra threads or heavy objects are created.
  • The onAwait clause registers a minimal callback with the Deferred's completion mechanism, triggering instantly when the task finishes.
  • Coroutine suspension/resumption is optimized by the framework, with minimal context-switching cost.

In practice, you won't notice any meaningful performance difference between using select and waiting for one Deferred—it's built for exactly this kind of use case.

Edge Cases to Keep in Mind

  • Exception Handling: If a Deferred throws an exception, select propagates it immediately. To wait for the first successful result, wrap each onAwait in try/catch and return nullable values, then filter out nulls.
  • Cancellation: Always cancel unused deferreds to avoid wasting CPU/memory on tasks you no longer need.

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

火山引擎 最新活动