У меня есть следующий фрагмент кода, использующий Kotlin Coroutines
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) используется внутри области async (вместо 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"); }
так как вы можете видеть, что последовательность начинается @
выше, которая описана в многопоточных потоках, ясно, что лямбда вызывается лениво. что означает, что тело лямбда не вызывается, если вызывающий не вызывает его.
результат неопределен в многопотоках, возможно, 6
или 8
, потому что он зависит от того, является ли это последовательностью @2
или последовательностью @4
. когда мы вызываем async(..)
чтобы запустить поток в пуле, потребуется немного времени, текущий поток не блокируется до тех пор, пока поток не будет запущен.
он также имеет проблему в javascript , например:
var seed = 3 function deferredResult() { return seed * 2 } seed = 4 var result = deferredResult() console.log("Result is " + result);// result is also 8
он решается путем введения в JavaScript javascript других вызовов inline анонимной функции. вы также можете решить его в kotlin, используя лямбда, как в javascript:
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
, особенно в коде, который включает любые формы параллелизма (сопрограммы, потоки или даже захват значений в lambdas).
В вашем конкретном примере вы должны объявить seed
как val
(неизменный), чтобы предотвратить случайную ошибку, изменив его позже. Фактически, компилятор должен был предупредить вас о том, что вы захватываете изменяемую переменную в сопрограмме coroutine, но эта функция в настоящее время не реализована. Смотрите билет KT-15515 .