今週は第70回を開催しました!
引き続きKotlin Hands-onをすすめています。
前回の様子はコチラ↓
[第70回の様子]
2021/6/30に第70回を開催した。
今週はちょっと人が減って、自分をいれて3名での開催。
PC不調のため自分は当面ナビゲータ役で参加。
勉強会本編の内容としては、Kotlin/Native Concurrencyハンズオンの続きを実施した。第5章Backgroundの後半を完了し、第6章Debuggingを半分ほど進めた。
[学んだことや疑問点]
- Kotlin/Native Concurrency: 5. Background
- Workerを利用する際にfreeze()するのはローカル変数の場合はまだ理解しやすいが、意図した以上の範囲をキャプチャした場合に問題となりやすい
- 以下のようなcaptuerTooMuch()を実行してみる
class CountingModel{
var count = 0
fun increment(){
count++
background {
saveToDb(count)
}
}
private fun saveToDb(arg:Int){
//Do some db stuff
println("Saving $arg to db")
}
}
fun captureTooMuch(){
val model = CountingModel()
model.increment()
println("I have ${model.count}")
model.increment()
println("I have ${model.count}") //We won't get here
}
- CountingModelオブジェクトを作成して、increment()を2回呼び出す:内部的にbackgroundでオブジェクトのプロパティをfreezeしているので、2回目の呼び出しはInvalidMutabilityExceptionが発生して失敗する
- わかりやすくするために、captureTooMuchAgain()を実行してみる
fun captureTooMuchAgain(){
val model = CountingModel()
println("model is frozen ${model.isFrozen}") // false
model.increment()
println("model is frozen ${model.isFrozen}") // true
}
- increment()を呼び出すとCountingModelのプロパティのcountがfreezeされるのだが、上記の結果からわかるように、親クラスのCountingModel自体もfreezeされている
- これを避けるためには、background呼び出しを関数化して、関数に渡す引数のみをfreezeするようにすればよい:以下のようにすると、argはメモリ上countプロパティとは別になるので、親クラスがfreezeされることもない
fun captureArgs(){
val model = CountingModelSafer()
model.increment()
println("I have ${model.count}")
model.increment()
println("I have ${model.count}")
}
class CountingModelSafer{
var count = 0
fun increment(){
count++
saveToDb(count)
}
private fun saveToDb(arg:Int) = background {
println("Doing db stuff with $arg, in main $isMainThread")
}
}
- という内容を踏まえて、実験を行ってみた:saveToDbの引数を参照型にしたらどうなるか
- 用意したのは以下のようなコード。increment()を2回呼び出すのは変わらず
class CountingModelSafer {
var state = InternalState(0)
fun increment() {
state.count++
saveToDb(state)
}
private fun saveToDb(arg: InternalState) = background {
println("Doing db stuff with ${arg.count}, in main $isMainThread")
}
}
data class InternalState(var count: Int)
- 上記の場合には、やはり我が友InvalidMutabilityExceptionが発生した
- しかし、以下の変更を加えると、問題なく動作した
fun increment() {
state.count++
saveToDb(state.clone()) // シャロークローンで別オブジェクトに
}
- シャロークローンだから、うまくいったが、プロパティに参照型が含まれる場合は?とさらに疑問が湧いたので実験してみる
- 用意したのは以下のようなクラス。(他の部分は省略、プルリクエストを参照。deepCopy()メソッドも実装されている)
data class InternalState(var count: Int, val state2: InternalState2)
- この場合はclone()してもダメで、clone時に追加でInternalState2をcloneすると、例外が発生しなくなった。
- 結論として、この手のメモリ管理はKotlinであまりやりたくないね、という話になった
- Kotlin/Native Concurrency: 6. Debugging
[まとめ]
モブプログラミング・スタイルで、Kotlin/Native Concurrencyハンズオンを進めた。
多少Native Concurrencyの理解が進んだ……気がする。
今週のプルリクエストはこちら。