コルーチン(Coroutine)は「非同期処理の手法」の1つです。
Kotlinが提供します。
コルーチンはビルダー(Builder)により開始されます。
ビルダーは3つの種類があり、その中の1つがlaunchです。
このlaunchビルダーについて、まとめます。
目次
ビルダーとは
ビルダー(Builder)は、CoroutineScopeを起点にスレッドを起動して、引数で指定されたタスクブロックを実行します。
つまり、ビルダーの役割はコルーチンを開始することです。
var scope: CoroutineScope? = null
findViewById<Button>(R.id.btnStart).setOnClickListener {
scope = SampleScope()
scope?.launch() { // launchビルダー
/* --- ここから --- */
/* 重い処理(長時間の処理) */
/* launch { ... } */
/* ^^^^^^^ タスクブロック */
println("Thread = ${Thread.currentThread().name}")
/* --- ここまで --- */
}
}
findViewById<Button>(R.id.btnCancel).setOnClickListener {
scope?.cancel()
}
class SampleScope : CoroutineScope {
override val coroutineContext: CoroutineContext =
Job() + Dispatchers.Default + CoroutineName("Hoge") // Contextを定義
}
launchの特徴
ビルダーは3つの種類があります。
その中のlaunchはタスクブロックの戻り値を返さないビルダーです。
| ビルダー | 概要 | 関数の戻り値 |
|---|---|---|
| launch | スレッドを起動してタスクブロックを実行 タスクブロックの戻り値なし スレッドをブロックしない(一時停止) | 起動したスレッドのJobインスタンス |
| async | スレッドを起動してタスクブロックを実行 タスクブロックの戻り値あり スレッドをブロックしない(一時停止) | 起動したスレッドのDeferredインスタンス ※DeferredはJobのサブクラス |
| runBlocking | スレッドを起動してタスクブロックを実行 タスクブロックの戻り値あり スレッドをブロックする(休止) | タスクブロックの戻り値 |
また、launchはCoroutineScopeに定義された拡張関数です。
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyStandaloneCoroutine(newContext, block) else
StandaloneCoroutine(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}
スレッドの起動(第3引数)
スレッドの起動はlaunchの引数(第3引数)にラムダ式(タスクブロック)を指定して実行するだけです。※引数の最後がラムダ式の場合、「( )」の外に出せる
この場合、起動されるスレッドはCoroutineScopeに実装されたCoroutineContextに従います。
// CoroutineScopeのCoroutineContextに従い、Defaultスレッドを起動
scope = SampleScope()
scope?.launch { // launchビルダー
println("Time = ${getTime()} Start Thread = ${getThread()}")
val _resultA = taskA()
println("Time = ${getTime()} ResultA = ${_resultA}")
println("Time = ${getTime()} End")
}
fun taskA(): String {
Thread.sleep(1000) // 重い処理の代わり
return "AAA" // 重い処理の戻り値
}
fun taskB(): String {
Thread.sleep(2000) // 重い処理の代わり
return "BBB" // 重い処理の戻り値
}
fun taskC(): String {
Thread.sleep(3000) // 重い処理の代わり
return "CCC" // 重い処理の戻り値
}

Time = 3901 Start Thread = DefaultDispatcher-worker-1 Time = 4910 ResultA = AAA Time = 4911 End
launchは入れ子が可能です。子コルーチンが開始できます。
// ビルダーの入れ子
scope = SampleScope()
scope?.launch { // launchビルダー
println("Time = ${getTime()} Start Thread = ${getThread()}")
launch { // launchビルダー
val _resultB = taskB()
println("Time = ${getTime()} ResultB = ${_resultB}")
val _resultC = taskC()
println("Time = ${getTime()} ResultC = ${_resultC}")
}
val _resultA = taskA()
println("Time = ${getTime()} ResultA = ${_resultA}")
println("Time = ${getTime()} End")
}

Time = 9137 Start Thread = DefaultDispatcher-worker-2 Time = 0142 ResultA = AAA Time = 0142 End Time = 1141 ResultB = BBB Time = 4143 ResultC = CCC
スレッドの切り替え(第1引数)
launchビルダーの第1引数はCoroutineContextです。
ここに新たなCoroutineContextを指定すると、CoroutineScopeに格納されたCoroutineContexへ加算(上書き)されます。つまり、引数で指定されたCoroutineContextが優先されます。
次の例は、Dispatchers.Mainを指定することで、スレッドプールをDefaultからMainに切り替える例です。launchによりMainスレッドが起動されます。
※CoroutineContextの詳細は「Kotlin:コルーチン(Coroutine)」を参照
// ビルダーの引数でプールを指定、Mainスレッドで起動
scope = SampleScope()
scope?.launch(Dispatchers.Main) { // launchビルダー
println("Time = ${getTime()} Start Thread = ${getThread()}")
val _resultA = taskA()
println("Time = ${getTime()} ResultA = ${_resultA}")
println("Time = ${getTime()} End")
}

Time = 3092 Start Thread = main Time = 4095 ResultA = AAA Time = 4095 End
並列処理(Parallel)
launchは実行される毎にスレッドを起動します。よって、連続して実行すれば、複数のスレッドが同時に起動して並列処理になります。
ただし、並列に動作するスレッド数は、プールが管理できる数に制限されます。
※DefaultブールはCore数まで管理可能
// 並列処理
scope = SampleScope()
scope?.launch(Dispatchers.Main) { // launchビルダー
println("Time = ${getTime()} Start Thread = ${getThread()}")
launch(Dispatchers.Default) { // launchビルダー
val _resultA = taskA()
println("Time = ${getTime()} ResultA = ${_resultA}")
}
launch(Dispatchers.Default) { // launchビルダー
val _resultB = taskB()
println("Time = ${getTime()} ResultB = ${_resultB}")
}
launch(Dispatchers.Default) { // launchビルダー
val _resultC = taskC()
println("Time = ${getTime()} ResultC = ${_resultC}")
}
println("Time = ${getTime()} End")
}

Time = 2808 Start Thread = main Time = 2810 End Time = 3811 ResultA = AAA Time = 4811 ResultB = BBB Time = 5811 ResultC = CCC
逐次処理(Serial)
Job#join( )はタスクブロックの終了を待ちます。join( )を使えば逐次処理が出来ます。
// 逐次処理
scope = SampleScope()
scope?.launch(Dispatchers.Main) { // launchビルダー
println("Time = ${getTime()} Start Thread = ${getThread()}")
launch(Dispatchers.Default) { // launchビルダー
val _resultA = taskA()
println("Time = ${getTime()} ResultA = ${_resultA}")
}.join()
launch(Dispatchers.Default) { // launchビルダー
val _resultB = taskB()
println("Time = ${getTime()} ResultB = ${_resultB}")
}.join()
launch(Dispatchers.Default) { // launchビルダー
val _resultC = taskC()
println("Time = ${getTime()} ResultC = ${_resultC}")
}.join()
println("Time = ${getTime()} End")
}

Time = 1713 Start Thread = main Time = 2716 ResultA = AAA Time = 4719 ResultB = BBB Time = 7724 ResultC = CCC Time = 7725 End
join( )はSuspend関数です。自身の実行後にスレッドを一時停止(Suspend)します。
そして、子コルーチンの処理が完了したところで、一時停止していたスレッドを再取得して、タスクブロックの続きの処理を再開します。
※Suspend関数の詳細は「Coroutine:Suspend関数とその仕組み」を参照
待ち合せ
joinAll( )はタスクブロックの終了を待つ関数です。
引数は可変長引数になっており、複数のJobインスタンスが指定できます。つまり、複数のタスクの終了を待ちます。
joinAll( )を使えば、「タスクAとBの終了を待ってタスクCの処理を行う」と言った、待ち合せを表現できます。
// タスク終了の待ち合せ( 1 )
scope = SampleScope()
scope?.launch(Dispatchers.Main) { // launchビルダー
println("Time = ${getTime()} Start Thread = ${getThread()}")
val _jobA = launch(Dispatchers.Default) { // launchビルダー
val _resultA = taskA()
println("Time = ${getTime()} ResultA = ${_resultA}")
}
val _jobB = launch(Dispatchers.Default) { // launchビルダー
val _resultB = taskB()
println("Time = ${getTime()} ResultB = ${_resultB}")
}
joinAll(_jobA, _jobB)
launch(Dispatchers.Default) {
val _resultC = taskC()
println("Time = ${getTime()} ResultC = ${_resultC}")
}
println("Time = ${getTime()} End")
}

Time = 1171 Start Thread = main Time = 2184 ResultA = AAA Time = 3182 ResultB = BBB Time = 3183 End Time = 6184 ResultC = CCC
joinAll( )はSuspend関数です。自身の実行後にスレッドを一時停止(Suspend)します。
中身は単純で、引数のJobインスタンスに対して、Job#join( )を一つ一つ実行しているだけです。
public suspend fun joinAll(vararg jobs: Job): Unit = jobs.forEach { it.join() }
※Suspend関数の詳細は「Coroutine:Suspend関数とその仕組み」を参照
スタートオプション(第2引数)
launchビルダーの第2引数はスタートオプションです。
コルーチンを開始する時の動作が指定できます。
| スタートオプション CoroutineStart.XXX | コルーチン 開始のタイミング | コルーチン キャンセルのタイミング |
|---|---|---|
| DEFAULT (引数なし) | ビルダーの実行後、直ちに開始する | 開始時にキャンセル可能 起動中にキャンセル可能 |
| LAZY | ビルダーの実行後、開始しない Job#start( )・Deferred#await( )で開始 | DEFAULTと同じ |
| ATOMIC | DEFAULTと同じ | 開始時にキャンセル不可 起動中にキャンセル可能 |
| UNDISPATCHED | DEFAULTと同じ | DEFAULTと同じ |
| 起動されるスレッドは Dispatchers.Unconfinedを指定して開始されるコルーチンと同じ |
||
引数がなければデフォルトのCroutineStart.DEFAULTが指定された事になります(拡張関数の定義より)。
CoroutineStart.LAZY
オプションLAZYはlaunchビルダーの実行後にコルーチンを開始しない動作です。
コルーチンの開始はJob#start( )関数によって行われます。
var _scope: CoroutineScope? = null
var _job: Job? = null
findViewById<Button>(R.id.btnReady).setOnClickListener { // Readyボタン
println("Time = ${getTime()} Ready coroutine !!")
_scope = SampleScope()
_scope?.launch(Dispatchers.Main) {
_job = launch(Dispatchers.Default, CoroutineStart.LAZY) {
println("Time = ${getTime()} Start TaskC Thread = ${getThread()}")
val _resultC = taskC()
println("Time = ${getTime()} End TaskC Result = ${_resultC}")
}
}
}
findViewById<Button>(R.id.btnStart).setOnClickListener { // Startボタン
println("Time = ${getTime()} Start coroutine !!")
_job?.start()
}
Time = 0827 Ready coroutine !! ... Readyボタン押下
:
〔時間経過〕
:
Time = 4698 Start coroutine !! ... Startボタン押下
Time = 4710 Start TaskC Thread = DefaultDispatcher-worker-1
... Job#Start( )で開始
Time = 7712 End TaskC Result = CCC
LAZYに対しDEFAULTはlaunchビルダーの実行後にコルーチンを開始する動作です。
var _scope: CoroutineScope? = null
var _job: Job? = null
findViewById<Button>(R.id.btnReady).setOnClickListener {
println("Time = ${getTime()} Ready coroutine !!")
_scope = SampleScope()
_scope?.launch(Dispatchers.Main) {
_job = launch(Dispatchers.Default, CoroutineStart.DEFAULT) {
println("Time = ${getTime()} Start TaskC Thread = ${getThread()}")
val _resultC = taskC()
println("Time = ${getTime()} End TaskC Result = ${_resultC}")
}
}
}
findViewById<Button>(R.id.btnStart).setOnClickListener {
println("Time = ${getTime()} Start coroutine !!")
_job?.start()
}
Time = 2276 Ready coroutine !! ... Readyボタン押下
Time = 2279 Start TaskC Thread = DefaultDispatcher-worker-1
... 直ちに開始
Time = 5282 End TaskC Result = CCC
CoroutineStart.ATOMIC
オプションATOMICはコルーチンの開始時にキャンセルしない動作です。
ただし、起動中のコルーチンはキャンセルできます。
以下のサンプルはDefaultプールを使って、10個のコルーチンの開始を試みます。しかし、プールで管理可能なスレッドは4つのため、直ちに開始されるコルーチンはTaskC_0,1,2,3になり、残りはスレッドが空くまで待機します。
この待機中にキャンセルの発行を行ったとしても、ATOMICの場合は待機中のコルーチンをキャンセルしません。スレッドに空きが出ればコルーチンを開始します。
var _scope: CoroutineScope? = null
findViewById<Button>(R.id.btnStart).setOnClickListener {
_scope = SampleScope()
_scope?.launch(Dispatchers.Main) {
for(i in 0..9) {
println("TaskC_${i} Start")
launch(Dispatchers.Default, CoroutineStart.ATOMIC) {
if(isActive) {
val _resultC = taskC()
println("TaskC_${i} = ${_resultC}")
}
else
println("TaskC_${i} Cancel!")
}
}
}
}
findViewById<Button>(R.id.btnCancel).setOnClickListener {
_scope?.cancel()
}
Time = 9730 TaskC_0 Start ... コルーチンの開始を試みる
Time = 9737 TaskC_1 Start
Time = 9745 TaskC_2 Start
Time = 9747 TaskC_3 Start
Time = 9750 TaskC_4 Start
Time = 9751 TaskC_5 Start
Time = 9751 TaskC_6 Start
Time = 9751 TaskC_7 Start
Time = 9752 TaskC_8 Start
Time = 9752 TaskC_9 Start
:
〔キャンセル発行〕
:
Time = 2746 TaskC_0 = CCC ... コルーチン開始、taskC( )が起動済み
Time = 2747 TaskC_4 Cancel! ... コルーチン開始、キャンセル状態⇒強制終了
Time = 2747 TaskC_5 Cancel!
Time = 2747 TaskC_6 Cancel!
Time = 2748 TaskC_7 Cancel!
Time = 2748 TaskC_8 Cancel!
Time = 2748 TaskC_1 = CCC
Time = 2749 TaskC_9 Cancel!
Time = 2762 TaskC_2 = CCC
Time = 2763 TaskC_3 = CCC
ATOMICに対しDEFAULTは待機中のコルーチンのキャンセルが行われます。
var _scope: CoroutineScope? = null
findViewById<Button>(R.id.btnStart).setOnClickListener {
_scope = SampleScope()
_scope?.launch(Dispatchers.Main) {
for(i in 0..9) {
println("TaskC_${i} Start")
launch(Dispatchers.Default, CoroutineStart.DEFAULT) {
if(isActive) {
val _resultC = taskC()
println("TaskC_${i} = ${_resultC}")
}
else
println("TaskC_${i} Cancel!")
}
}
}
}
findViewById<Button>(R.id.btnCancel).setOnClickListener {
_scope?.cancel()
}
Time = 5509 TaskC_0 Start
Time = 5519 TaskC_1 Start
Time = 5523 TaskC_2 Start
Time = 5524 TaskC_3 Start
Time = 5525 TaskC_4 Start
Time = 5526 TaskC_5 Start
Time = 5526 TaskC_6 Start
Time = 5526 TaskC_7 Start
Time = 5527 TaskC_8 Start
Time = 5527 TaskC_9 Start
:
〔キャンセル発行〕
:
Time = 8522 TaskC_0 = CCC ... コルーチン開始、taskC( )が起動済み
Time = 8524 TaskC_1 = CCC
Time = 8526 TaskC_2 = CCC
Time = 8526 TaskC_3 = CCC
... 残りはコルーチン開始時にキャンセル
関連記事:
