コルーチン(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までメモリにゴミが残る
です。
コルーチンはスレッドをプールから再利用するので、上記の欠点がありません。よくコルーチンのメリットとして述べられている点です。
関連記事:
続きを読む
続きを読む
続きを読む
続きを読む
続きを読む
続きを読む
続きを読む
続きを読む
続きを読む