Skip to content
Update Kotlin coroutines authored by umaumax's avatar umaumax
......@@ -5,6 +5,205 @@
* 新しいコルーチンを作成して、即座に実行を開始する
* 返り値の返却はできない
## async
* `async`ブロックは非同期タスク(Deferred オブジェクト)を生成し、`await`で返り値を受け取る
### 複数のasyncブロックの非同期タスクを生成して実行するコード例
下記のコードでは、asyncブロックの非同期タスクの結果はforループの単位(100単位)で表示される
``` kotlin
import kotlinx.coroutines.*
// import kotlin.system.exitProcess
fun main(args: Array<String>) = runBlocking {
val numberOfBlocks = 100
var x = 0
val jobs =
List(numberOfBlocks) {
async {
println("start")
// yield() // ここでコルーチンが切り替わるとstartが連続して表示される
for (i in 1..100) {
// val pre_x = x
// yield() // ここでコルーチンが切り替わるとresultの値が1単位で変化する可能性が非常に高い かつ この行の前後の`x`が代わり得る
x += 1
// if (pre_x + 1 != x) {
// exitProcess(0)
// }
}
println("end")
x
}
}
val results = jobs.awaitAll()
for (i in 0 until numberOfBlocks) {
println("async block[$i]'s result: ${results[i]}")
}
}
```
上記の`x`は1スレッドに限定されたコルーチン内で動作する限りは正しい結果を返すが、複数スレッドで同時実行されるコルーチンとなると、`yield()`の有無に関係なく未定義な動作となる
`val dispatcher = newFixedThreadPoolContext(10, "HogeThread")`
を宣言し、`async()``async(dispatcher)`へ変更して、
`dispatcher.close()`をすることで、スレッドプールでコルーチンを実行して確かめることができる
`import java.util.concurrent.atomic.*`を宣言し、
`var x = 0``var x = AtomicInteger()`
`x += 1``x.incrementAndGet()`とすると、期待した結果となる(ただし、awaitの返り値はすべて10000である可能性が高い)
## Dispatchers
[Dispatchers.Default]( https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html )
スレッドの最大数はCPUコアの数と同じ(ただし、2並列以上)
デフォルトで提供されるディスパッチャーでコルーチンを実行させたい場合は`async(Dispatchers.Default)``withContext(Dispatchers.Default)``launch(Dispatchers.Default)`などとする
[Dispatchers.Main]( https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-main.html )
これはmain関数を実行しているスレッドではなく、UIを制御しているスレッドを指す
``` kotlin
launch() { // context of the parent
println("launch(): ${Thread.currentThread().name}")
}
launch(Dispatchers.Unconfined) { // not confined -- context of the parent(ただし、最初のsuspend地点まで現在のコンテキストで、それ以降のコンテキストは保証されない)
println("launch(Dispatchers.Unconfined): ${Thread.currentThread().name}")
}
launch(Dispatchers.Default) { // will get dispatched to DefaultDispatcher
println("launch(Dispatchers.Default): ${Thread.currentThread().name}")
}
```
出力(異なるスレッドで実行されるので、ランダムな出力順番となる)
``` bash
launch(Dispatchers.Unconfined): main
launch(Dispatchers.Default): DefaultDispatcher-worker-1
launch(): main
```
[Coroutine context and dispatchers | Kotlin Documentation]( https://kotlinlang.org/docs/coroutine-context-and-dispatchers.html#unconfined-vs-confined-dispatcher )
`Dispatchers.Unconfined`は最初のsuspend地点まで現在のコンテキストで、それ以降のコンテキストは保証されない
``` kotlin
launch(Dispatchers.Unconfined) { // not confined -- will work with main thread
println("Unconfined : I'm working in thread ${Thread.currentThread().name}")
delay(500)
println("Unconfined : After delay in thread ${Thread.currentThread().name}")
}
launch { // context of the parent, main runBlocking coroutine
println("main runBlocking: I'm working in thread ${Thread.currentThread().name}")
delay(1000)
println("main runBlocking: After delay in thread ${Thread.currentThread().name}")
}
```
``` bash
Unconfined : I'm working in thread main
main runBlocking: I'm working in thread main
Unconfined : After delay in thread kotlinx.coroutines.DefaultExecutor
main runBlocking: After delay in thread main
```
## コルーチンの並列度
rustのtokioと同じように明示的に別のスレッドを利用しない限り、コルーチンの同時実行数は1である
## スレッドプールを作成してコルーチンを実行する例
``` kotlin
import kotlinx.coroutines.*
fun main() = runBlocking {
// このスレッドプール数を1にしても、mainスレッドとは別のスレッドで実行されることとなる
val dispatcher1 = newFixedThreadPoolContext(3, "HogeThread")
val dispatcher2 = newFixedThreadPoolContext(2, "FugaThread")
println("main thread: ${Thread.currentThread().name}(id:${Thread.currentThread().getId()})")
val job1 =
async(dispatcher1) {
for (i in 1..9) {
println("Job Hoge: $i, thread: ${Thread.currentThread().name}(id:${Thread.currentThread().getId()})")
}
}
val job2 =
async(dispatcher2) {
for (i in 1..9) {
println("Job Fuga: $i, thread: ${Thread.currentThread().name}(id:${Thread.currentThread().getId()})")
}
}
job1.await()
job2.await()
dispatcher1.close()
dispatcher2.close()
}
```
``` bash
main thread: main(id:1)
Job Hoge: 1, thread: HogeThread-1(id:14)
Job Hoge: 2, thread: HogeThread-1(id:14)
Job Hoge: 3, thread: HogeThread-1(id:14)
Job Hoge: 4, thread: HogeThread-1(id:14)
Job Hoge: 5, thread: HogeThread-1(id:14)
Job Fuga: 1, thread: FugaThread-1(id:15)
Job Fuga: 2, thread: FugaThread-1(id:15)
Job Hoge: 6, thread: HogeThread-1(id:14)
Job Hoge: 7, thread: HogeThread-1(id:14)
Job Fuga: 3, thread: FugaThread-1(id:15)
Job Fuga: 4, thread: FugaThread-1(id:15)
Job Fuga: 5, thread: FugaThread-1(id:15)
Job Fuga: 6, thread: FugaThread-1(id:15)
Job Fuga: 7, thread: FugaThread-1(id:15)
Job Hoge: 8, thread: HogeThread-1(id:14)
Job Hoge: 9, thread: HogeThread-1(id:14)
Job Fuga: 8, thread: FugaThread-1(id:15)
Job Fuga: 9, thread: FugaThread-1(id:15)
```
## 単一のスレッドでコルーチンを実行する
`@OptIn`が必要となるので、`val dispatcher = newFixedThreadPoolContext(1, "HogeThread")`としたほうがよいかもしれない
これは単一のスレッドで実行することで、コルーチンで安全に変数を操作できる(主にUIアプリケーションで使わている)
``` kotlin
import kotlinx.coroutines.*
@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
fun main(args: Array<String>) = runBlocking {
val singleContext = newSingleThreadContext("singleContext")
withContext(singleContext) {
println("hoge")
}
}
```
## コンテキストの切り替えタイミング
連続して複数のasyncタスクをawaitで待ち受けるときにasyncブロックの非同期タスクの冒頭で、`yield()``delay()`を利用して切り替えるタイミングの有無で挙動が変わる
例えば、切り替えタイミングがない場合は
```
start 1
end 1
start 2
end 2
start 3
end 3
```
となるが、切り替えタイミングがある場合は
```
start 1
start 2
start 3
end 1
end 2
end 3
```
となる
## トラブルシューティング
### Unresolved reference: launch
* `launch``runBlocking``coroutineScope`などのスコープ内でしか利用できない
......
......