在 Kotlin 中,如何传递参数以便异步作用域保存它?

In Kotlin, how do I pass a parameter so that the async scope will conserve it?

我有以下使用 Kotlin 协程的代码片段

fun main(args:Array<String>){
  println("test")

  var seed = 3
  val deferredResult = async(CommonPool){
    seed * 2
  }

  seed = 4

  runBlocking(CommonPool) {
    val result = deferredResult.await()
    println("Result is $result")
  }

  println("end")
}

我期望它的行为像 javascript 并在定义协程时保留 seed 变量的值(使用副本)。但不是打印 Result is 6,而是打印 Result is 8.

如何确保在异步范围内使用种子变量的原始值(即 3)(而不是 4)?

让我们看一个非多线程的例子,这样会更清楚:

fun main(args:Array<String>){
    println("test")

    var seed = 3 // @1. initializing seed=3
    val deferredResult = {
        seed * 2 // @4. seed=4 then 
    }

    seed = 4  // @2. reassign seed=4

    //            v--- @3. calculates the result
    val result = deferredResult()
    //    ^---  8
    println("Result is $result");
}

如您所见,序列从上面的 @ 开始,在非多线程中清楚地描述了 lambda 是延迟调用的。这意味着除非调用者调用它,否则不会调用 lambda 的主体。

在多线程中结果不确定,可能是6也可能是8,因为要看是序列@2还是序列@4第一次被调用。当我们调用 async(..) 在池中启动一个线程时需要一些时间,当前线程不会阻塞,直到线程为 运行.

它在 中也有问题,例如:

var seed = 3

function deferredResult() {
    return seed * 2
}

seed = 4

var result = deferredResult()
console.log("Result is " + result);// result is also 8

通过在javascript中引入另一个调用内联匿名函数来解决。您也可以像 javascript:

那样使用 lambda 在 kotlin 中解决它
fun main(args: Array<String>) {
    println("test")

    var seed = 3
    //                    v--- like as javascript (function(seed){...})(seed);
    val deferredResult = ({ seed: Int ->
        async(CommonPool) {
            seed * 2
        }
    })(seed);

    seed = 4

    runBlocking(CommonPool) {
        val result = deferredResult.await()
        //   ^--- result is always 6 now
        println("Result is $result")
    }

    println("end")
}

您应该避免使用 var,尤其是在涉及任何形式的并发(协程、线程,甚至将值捕获到 lambda 中)的代码中。

在您的特定示例中,您应该将 seed 声明为 val(不可变),以防止以后更改它时出现意外错误。事实上,编译器应该警告过您正在将可变变量捕获到协程中,但目前尚未实现此功能。见票KT-15515.