コルーチン(Coroutine)は「非同期処理の手法」の1つです。
Kotlinが提供します。
特徴としてnon-blocking動作をサポートします。
このnon-blocking動作についてまとめます。
目次
delay( )とThread.sleep( )の違い
サンプル(A)(B)は「3⇒2⇒1⇒0」と1秒の間隔でカウントダウンを行うプログラムです。
1秒の間隔を作るために、1つは(A)コルーチンのdelay( )を使い、もう1つは(B)Thread.sleep( )を使っています。(B)は単にdelay( )をThread.sleep( )に置き換えたものです。
val _txtCount = findViewById<TextView>(R.id.txtCount)
val _btnStart = findViewById<Button>(R.id.btnStart)
val _btnStop = findViewById<Button>(R.id.btnStop)
var _job: Job? = null
_btnStart.setOnClickListener {
_job?.cancel() // 起動が重複した時、前のタスクをキャンセル
_job = GlobalScope.launch(Dispatchers.Main) { // Mainスレッドを起動
_txtCount.text = "3"
delay(1000) // 1秒の間隔を作る
_txtCount.text = "2"
delay(1000) // 1秒の間隔を作る
_txtCount.text = "1"
delay(1000) // 1秒の間隔を作る
_txtCount.text = "0"
}
}
_btnStop.setOnClickListener {
_job?.cancel()
}
val _txtCount = findViewById<TextView>(R.id.txtCount)
val _btnStart = findViewById<Button>(R.id.btnStart)
val _btnStop = findViewById<Button>(R.id.btnStop)
var _job: Job? = null
_btnStart.setOnClickListener {
_job?.cancel() // 起動が重複した時、前のタスクをキャンセル
_job = GlobalScope.launch(Dispatchers.Main) { // Mainスレッドを起動
_txtCount.text = "3"
Thread.sleep(1000) // 1秒の間隔を作る
_txtCount.text = "2"
Thread.sleep(1000) // 1秒の間隔を作る
_txtCount.text = "1"
Thread.sleep(1000) // 1秒の間隔を作る
_txtCount.text = "0"
}
}
_btnStop.setOnClickListener {
_job?.cancel()
}

(A)コルーチンのdelay( )を使った例は正常に動きますが、(B)Thread.sleep( )を使った例はSTARTボタンを押してもカウントダウンしません。
delay( )もThread.sleep( )も「1秒の間隔を作る」構文であることに、違いは無いのですが…
これはスレッドの扱われ方の違いから生じています。
non-blocking動作のdelay( )
delay( )は実行後にスレッドを一時停止(suspend)します。
一時停止したスレッドはプールに返却されて、新たな処理へ割り当てられるまで待機します。処理の要求があれば、プールから取得したスレッドへ割り当てます。
つまり、スレッドは新たな処理の割り当てを拒否しません。
これをnon-blocking動作といいます。

サンプル(A)は1秒の間隔を作る間に画面描画(新たな処理)が実行できるので、アプリは正常に動作します。
blocking動作のThread.sleep( )
sleep( )は実行後にスレッドを休止(sleep)します。
休止したスレッドは「休止」という処理を続けています。稼働した状態と同じで、新たな処理が割り当てできません。
つまり、スレッドは新たな処理の割り当てを拒否します。
これをblocking動作といいます。

コルーチンを処理している間、スレッドはシステムが発行する画面描画の実行も拒否します。サンプル(B)は、これが原因で画面は更新されず、アプリは何も変化しない見た目になります。
Susupend関数とは
non-blocking動作を含んでいる関数のことをSuspend関数と呼びます。
Suspend関数は関数名の前にsuspend修飾子を付ける決まりになっています。
delay( )はSuspend関数です。ですので、suspend修飾子が付いています。
public suspend fun delay(timeMillis: Long) {
if (timeMillis <= 0) return // don`t delay
return suspendCancellableCoroutine sc@ { cont: CancellableContinuation<Unit> ->
cont.context.delay.scheduleResumeAfterDelay(timeMillis, cont)
}
}
※詳細は「Susupend関数とその仕組み」を参照
non-blocking動作≒「スレッド+コールバック」
コルーチンのSuspend関数が利用可能になる以前は、non-blocking動作を「スレッド+コールバック」で表現していました。
例えばdelay( )は次のようになります。
_txtCount.text = "2"
delay(1000)
_txtCount.text = "1"
_txtCount.text = "2"
_thread = Thread { // スレッド
try { Thread.sleep(1000) }
catch (e: InterruptedException) { return@Thread }
runOnUiThread { // コールバック
_txtCount.text = "1"
}
}.apply { start() }
「スレッド+コールバック」の欠点は、
(1)non-blocking動作毎にThreadのインスタンスを作成 ⇒起動のオーバーヘッドが大きい (2)コールバック後にThreadインスタンスは不要 ⇒破棄される、GCまでメモリにゴミが残る
です。
コルーチンはスレッドをプールから再利用するので、上記の欠点がありません。よくコルーチンのメリットとして述べられている点です。
関連記事:
